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

getUser() does not work in new SSR package from Next.js API routes #26249

Open
2 tasks done
oldbettie opened this issue May 13, 2024 · 4 comments
Open
2 tasks done

getUser() does not work in new SSR package from Next.js API routes #26249

oldbettie opened this issue May 13, 2024 · 4 comments
Labels
auth All thing Supabase Auth related bug Something isn't working

Comments

@oldbettie
Copy link

oldbettie commented May 13, 2024

Bug report

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:

  1. When logged into supabase, Then call an api route with axios or fetch
  2. Call getUser() within the api route. It will get a user
  3. 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

@oldbettie oldbettie added the bug Something isn't working label May 13, 2024
@d-e-h-i-o
Copy link

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".

@charislam charislam added the auth All thing Supabase Auth related label May 13, 2024
@oldbettie
Copy link
Author

oldbettie commented May 14, 2024

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

@oldbettie
Copy link
Author

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.

@harrybawsac
Copy link

harrybawsac commented Jun 1, 2024

@oldbettie

Did your try step 4 from the Supabase guide? The "updateSession" function might be the thing you're looking for maybe?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auth All thing Supabase Auth related bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants