getUser() does not work in new SSR package from Next.js API routes
oldbettie opened this issue · comments
Bug report
- I confirm this is a bug with Supabase, not with my own application.
- I confirm I have searched the Docs, GitHub Discussions, and Discord.
https://discord.com/channels/839993398554656828/1239573574214357033
Describe the bug
I have recently started updating some of my SaaS projects to use the new SSR package. Apon my first update I noticed many of my API routes broke.
I use Next.js 14 app router with routeHandlers for handling all my data.
YES I call await supabase.auth.getUser();
in the middleware
I use the supabase.auth.getUser()
function to retrieve the user before doing anything within an api route. This allows me to validate they are logged in and have correct access.
The session exists in every other instance of the package. serverActions, client, and server components. It is only API routes that are effected.
I have even tried to pass both the access_token
and refresh_token
to the api route and use setSession
apon next attempting to use supabase it does not work even within the same api route.
Code
"use server"
import axios from "axios"
import { getBaseUrl, ServerSecureHeaders } from "@/lib/serverUtils"
import { supabaseServerComponentClient } from "@/lib/supabase/supabaseServerComponentClient"
import { redirect } from "next/navigation"
export default async function UserDashboardPage() {
const supabase = supabaseServerComponentClient()
const {
data: { user },
error,
} = await supabase.auth.getUser()
if (!user || error) return redirect(`/login${error ? `?message=${error.message}` : `No user`}`)
const response = await axios.get(`${getBaseUrl()}/api/v1/users/${user.id}`, {
headers: {
...ServerSecureHeaders,
// I thied adding the tokens here to create a session
},
})
console.log(response.data)
const data = response.data.data
return (
<div className="md:w-narrow-content w-full my-5 mx-auto">
<h1>User Dashbaord</h1>
<br />
{/*{data}*/}
</div>
)
}
export const ServerSecureHeaders = ((): Partial<RawAxiosRequestHeaders> => {
return {
"Content-Type": "application/json",
"Accept": "application/json",
"x-api-key": env.PROTECTED_API_KEY,
"Cache-Control": "no-cache"
}
})()
export async function GET(req: NextRequest, { params }: { params: { slug: string } }) {
try {
const supabase = supabaseServerClient()
const token = req.headers.get("user-jwt-token")
if (!token) throw new Error("no jwt tokens")
const {access_token, refresh_token} = JSON.parse(token) as { access_token: string, refresh_token: string }
const {
data: { user },
error,
} = await supabase.auth.setSession({ access_token, refresh_token })
const userData = await getUserInfo(supabase, params.slug)
return NextResponse.json({ data: userData, error: null }, { status: 200 })
} catch (e: any) {
console.warn(e)
return NextResponse.json({ data: null, error: "Internal Server Error" }, { status: 500 })
}
}
getUserInfo simply should return the user info in our system
To Reproduce
Steps to reproduce the behavior, please provide code snippets or a repository:
- When logged into supabase, Then call an api route with axios or fetch
- Call
getUser()
within the api route. It will get a user - call getSession or any other supabase request and you will receive null
Expected behavior
Calling getUser()
should perform as it did in go-true it should always return the currently logged in user if they exist.
System information
- OS: macOS
- Browser (Chrome)
- "@supabase/ssr": "^0.3.0",
- "@supabase/supabase-js": "^2.42.7",
- Version of Node.js: [e.g. 18+]
Additional context
I have tried multiple different ways to go about this including manipulation the session manually. I would expect getUser
to just work as it did
We're also experiencing problems that in our middleware, await this.supabaseAdminClient.auth.getUser(jwtToken)
sometimes returns null.
We're on "@supabase/supabase-js": "^2.39.8"
.
I was able to peice together a solution following this blog post https://jonmeyers.io/blog/forwarding-cookies-from-server-components-to-route-handlers-with-next-js-app-router
How ever i was not able to manually create a session with the tokens from an api route. I think this is still a bug and so I will not close this issue
EDIT:
This is not actually a fix. This fixes the session existing in API routes but since the headers()
is read only it is impossible to pass anything else to the api route or for that matter the middleware which we use for verifying api access. This is not going to work so the problem still persists
Setting an environment variable in the next.config for every route also does not work to pass into the headers. setting it for every route would leak api keys to all requests
I came up with an ok solution it is not perfect but it works. This uses getSession
from the server page and passes the jwt to the api route. I then verify it and return an instance of supabase with the session intact. This can then be used in any API route aslong as the session is included in the request.
export async function supabaseClientWithVerifiedSession(req: NextRequest) {
const supabase = supabaseServerClient()
const token = req.headers.get("jwt-token")
if (!token) throw new Error("No token provided")
const { access_token, refresh_token } = JSON.parse(token) as { access_token: string; refresh_token: string }
jwt.verify(access_token, env.SUPABASE_JWT_SECRET)
const { error } = await supabase.auth.setSession({ access_token, refresh_token })
if (error) throw error
return supabase
}
The solution in my previous comment does not work due to not being able to modify the headers()
function. We have a few microservices that all use other security headers so not being able to add headers was not going to work for us. This way allows us to have a standalone API with an active session. We also have a mobile app so we can in the future use the JWT to get data on the app.