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

Minor bug in Server-Side Auth for Next.js docs #26279

Open
RichardHruby opened this issue May 14, 2024 · 1 comment
Open

Minor bug in Server-Side Auth for Next.js docs #26279

RichardHruby opened this issue May 14, 2024 · 1 comment
Labels
documentation Improvements or additions to documentation pr-opened A PR has been opened to resolve the issue

Comments

@RichardHruby
Copy link

RichardHruby commented May 14, 2024

Improve code snippet in documentation

Link

The code snippet under step 7 on this docs page

Describe the problem

TL;DR: When using GCP Cloud Run and Docker to deploy a NextJS app to production, the code snippet under step 7 redirects to the URL and port exposed by the docker container, instead of the external facing URL of the app.

Reproduce / see in action:

  1. Insert console logs in auth/confirm/route.ts and Deploy NextJS app via docker.
  2. Sign up a test user and check the confirmation link they receive via email: e.g. https://supabase-debug-c27oq5sqda-uc.a.run.app/auth/confirm?token_hash=pkce_633178ae4dbfcf566d5bba76adf2405c063a3713f07ced81d797a528&type=signup
  3. Click the link. You will get redirected to localhost:3000 or the exposed port.
  4. Check logs below:

As you can see from the logs, the incoming GET request is coming from localhost:3000. It contains the search parameters. We are able to successfully extrac the JWT for the confirmation of the email address. Still, since/auth/confirm/route.ts is executed on the server side, we get redirected to localhost:3000, where docker is exposing the client, instead of the URL where the app is deployed.

Therefore return NextResponse.redirect(redirectTo) will redirect to localhost.

Describe the improvement

The issue can simply be fixed by using redirect() from next/navigation.

Instead of return NextResponse.redirect(redirectTo) in line 26 we can use redirect(next) after importing import { redirect } from 'next/navigation'

See the proposed code snippet for auth/confirm/route.ts for step 7 of the docs page

import { type EmailOtpType } from '@supabase/supabase-js'
import { type NextRequest, NextResponse } from 'next/server'

import { createClient } from '@/utils/supabase/server'
import { redirect } from 'next/navigation'

export async function GET(request: NextRequest) {
  
  const { searchParams } = new URL(request.url)
  const token_hash = searchParams.get('token_hash')
  const type = searchParams.get('type') as EmailOtpType | null
  const next = searchParams.get('next') ?? '/'

  if (token_hash && type) {
    const supabase = createClient()

    const { error } = await supabase.auth.verifyOtp({
      type,
      token_hash,
    })
    if (!error) {
      // redirect user to specified redirect URL or root of app
      redirect(next)
    }
  }

  // redirect the user to an error page with some instructions
  redirect("/error")
}

This will redirect to the specified redirect URL or the global root of the deployed app, irrespective of the internal or external URL.

Additional context

Example code:

import { type EmailOtpType } from '@supabase/supabase-js'
import { type NextRequest, NextResponse } from 'next/server'

import { createClient } from '@/utils/supabase/server'

export async function GET(request: NextRequest) {
  console.log("INCOMING REQUEST:")
  console.log(request)
  
  const { searchParams } = new URL(request.url)
  const token_hash = searchParams.get('token_hash')
  const type = searchParams.get('type') as EmailOtpType | null
  const next = searchParams.get('next') ?? '/'

  const redirectTo = request.nextUrl.clone()
  redirectTo.pathname = next
  redirectTo.searchParams.delete('token_hash')
  redirectTo.searchParams.delete('type')

  if (token_hash && type) {
    const supabase = createClient()

    const { error } = await supabase.auth.verifyOtp({
      type,
      token_hash,
    })
    if (!error) {
      redirectTo.searchParams.delete('next')
      
      console.log("REDIRECTING TO:")
      console.log(redirectTo)

      return NextResponse.redirect(redirectTo)
    }
  }

  // return the user to an error page with some instructions
  redirectTo.pathname = '/error'
  return NextResponse.redirect(redirectTo)
}

Example docker file:

FROM node:18-alpine AS base
WORKDIR /app

# Copy package files and install dependencies
COPY package*.json ./
RUN npm install

# Copy the rest of your application source code
COPY . .

# Build your Next.js application
RUN npm run build

# Expose the port the app runs on
EXPOSE 3000

# Command to start your application
CMD ["npm", "start"]

Example logs:

INFO 2024-05-13T23:47:55.261597Z [httpRequest.requestMethod: GET] [httpRequest.status: 307] [httpRequest.responseSize: 3.37 KiB] [httpRequest.latency: 402 ms] [httpRequest.userAgent: Chrome 124.0.0.0] https://supabase-debug-c27oq5sqda-uc.a.run.app/auth/confirm?token_hash=pkce_633178ae4dbfcf566d5bba76adf2405c063a3713f07ced81d797a528&type=signup
DEFAULT 2024-05-13T23:47:55.305860Z INCOMING REQUEST:
DEFAULT 2024-05-13T23:47:55.312560Z NextRequest [Request] {
DEFAULT 2024-05-13T23:47:55.312571Z [Symbol(realm)]: {
DEFAULT 2024-05-13T23:47:55.312577Z settingsObject: { baseUrl: undefined, origin: [Getter], policyContainer: [Object] }
DEFAULT 2024-05-13T23:47:55.312582Z },
DEFAULT 2024-05-13T23:47:55.312586Z [Symbol(state)]: {
DEFAULT 2024-05-13T23:47:55.312590Z method: 'GET',
DEFAULT 2024-05-13T23:47:55.312593Z localURLsOnly: false,
DEFAULT 2024-05-13T23:47:55.312597Z unsafeRequest: false,
DEFAULT 2024-05-13T23:47:55.312600Z body: null,
DEFAULT 2024-05-13T23:47:55.312604Z client: { baseUrl: undefined, origin: [Getter], policyContainer: [Object] },
DEFAULT 2024-05-13T23:47:55.312607Z reservedClient: null,
DEFAULT 2024-05-13T23:47:55.312610Z replacesClientId: '',
DEFAULT 2024-05-13T23:47:55.312615Z window: 'client',
DEFAULT 2024-05-13T23:47:55.312618Z keepalive: false,
DEFAULT 2024-05-13T23:47:55.312621Z serviceWorkers: 'all',
DEFAULT 2024-05-13T23:47:55.312625Z initiator: '',
DEFAULT 2024-05-13T23:47:55.312628Z destination: '',
DEFAULT 2024-05-13T23:47:55.312633Z priority: null,
DEFAULT 2024-05-13T23:47:55.312636Z origin: 'client',
DEFAULT 2024-05-13T23:47:55.312640Z policyContainer: 'client',
DEFAULT 2024-05-13T23:47:55.312644Z referrer: 'client',
DEFAULT 2024-05-13T23:47:55.312649Z referrerPolicy: '',
DEFAULT 2024-05-13T23:47:55.312659Z mode: 'cors',
DEFAULT 2024-05-13T23:47:55.312665Z useCORSPreflightFlag: false,
DEFAULT 2024-05-13T23:47:55.312668Z credentials: 'same-origin',
DEFAULT 2024-05-13T23:47:55.312672Z useCredentials: false,
DEFAULT 2024-05-13T23:47:55.312675Z cache: 'default',
DEFAULT 2024-05-13T23:47:55.312678Z redirect: 'follow',
DEFAULT 2024-05-13T23:47:55.312680Z integrity: '',
DEFAULT 2024-05-13T23:47:55.312684Z cryptoGraphicsNonceMetadata: '',
DEFAULT 2024-05-13T23:47:55.312692Z parserMetadata: '',
DEFAULT 2024-05-13T23:47:55.312695Z reloadNavigation: false,
DEFAULT 2024-05-13T23:47:55.312698Z historyNavigation: false,
DEFAULT 2024-05-13T23:47:55.312701Z userActivation: false,
DEFAULT 2024-05-13T23:47:55.312704Z taintedOrigin: false,
DEFAULT 2024-05-13T23:47:55.312707Z redirectCount: 0,
DEFAULT 2024-05-13T23:47:55.312711Z responseTainting: 'basic',
DEFAULT 2024-05-13T23:47:55.312714Z preventNoCacheCacheControlHeaderModification: false,
DEFAULT 2024-05-13T23:47:55.312717Z done: false,
DEFAULT 2024-05-13T23:47:55.312720Z timingAllowFailed: false,
DEFAULT 2024-05-13T23:47:55.312724Z headersList: HeadersList {
DEFAULT 2024-05-13T23:47:55.312727Z cookies: null,
DEFAULT 2024-05-13T23:47:55.312730Z [Symbol(headers map)]: [Map],
DEFAULT 2024-05-13T23:47:55.312733Z [Symbol(headers map sorted)]: [Array]
DEFAULT 2024-05-13T23:47:55.312736Z },
DEFAULT 2024-05-13T23:47:55.312739Z urlList: [ [URL] ],
DEFAULT 2024-05-13T23:47:55.312742Z url: URL {
DEFAULT 2024-05-13T23:47:55.312746Z href: 'https://localhost:3000/auth/confirm?token_hash=pkce_633178ae4dbfcf566d5bba76adf2405c063a3713f07ced81d797a528&type=signup',
DEFAULT 2024-05-13T23:47:55.312749Z origin: 'https://localhost:3000',
DEFAULT 2024-05-13T23:47:55.312752Z protocol: 'https:',
DEFAULT 2024-05-13T23:47:55.312755Z username: '',
DEFAULT 2024-05-13T23:47:55.312759Z password: '',
DEFAULT 2024-05-13T23:47:55.312762Z host: 'localhost:3000',
DEFAULT 2024-05-13T23:47:55.312766Z hostname: 'localhost',
DEFAULT 2024-05-13T23:47:55.312769Z port: '3000',
DEFAULT 2024-05-13T23:47:55.312772Z pathname: '/auth/confirm',
DEFAULT 2024-05-13T23:47:55.312775Z search: '?token_hash=pkce_633178ae4dbfcf566d5bba76adf2405c063a3713f07ced81d797a528&type=signup',
DEFAULT 2024-05-13T23:47:55.312779Z searchParams: URLSearchParams {
DEFAULT 2024-05-13T23:47:55.312782Z 'token_hash' => 'pkce_633178ae4dbfcf566d5bba76adf2405c063a3713f07ced81d797a528',
DEFAULT 2024-05-13T23:47:55.312785Z 'type' => 'signup' },
DEFAULT 2024-05-13T23:47:55.312789Z hash: ''
DEFAULT 2024-05-13T23:47:55.312791Z }
DEFAULT 2024-05-13T23:47:55.312795Z },
DEFAULT 2024-05-13T23:47:55.312797Z [Symbol(signal)]: AbortSignal { aborted: false },
DEFAULT 2024-05-13T23:47:55.312800Z [Symbol(abortController)]: AbortController { signal: AbortSignal { aborted: false } },
DEFAULT 2024-05-13T23:47:55.312804Z [Symbol(headers)]: HeadersList {
DEFAULT 2024-05-13T23:47:55.312807Z cookies: null,
DEFAULT 2024-05-13T23:47:55.312809Z [Symbol(headers map)]: Map(29) {
DEFAULT 2024-05-13T23:47:55.312812Z 'accept' => [Object],
DEFAULT 2024-05-13T23:47:55.312815Z 'accept-encoding' => [Object],
DEFAULT 2024-05-13T23:47:55.312818Z 'accept-language' => [Object],
DEFAULT 2024-05-13T23:47:55.312821Z 'cookie' => [Object],
DEFAULT 2024-05-13T23:47:55.312824Z 'forwarded' => [Object],
DEFAULT 2024-05-13T23:47:55.312832Z 'host' => [Object],
DEFAULT 2024-05-13T23:47:55.312836Z 'priority' => [Object],
DEFAULT 2024-05-13T23:47:55.312840Z 'sec-ch-ua' => [Object],
DEFAULT 2024-05-13T23:47:55.312844Z 'sec-ch-ua-arch' => [Object],
DEFAULT 2024-05-13T23:47:55.312849Z 'sec-ch-ua-bitness' => [Object],
DEFAULT 2024-05-13T23:47:55.312853Z 'sec-ch-ua-full-version-list' => [Object],
DEFAULT 2024-05-13T23:47:55.312857Z 'sec-ch-ua-mobile' => [Object],
DEFAULT 2024-05-13T23:47:55.312860Z 'sec-ch-ua-model' => [Object],
DEFAULT 2024-05-13T23:47:55.312863Z 'sec-ch-ua-platform' => [Object],
DEFAULT 2024-05-13T23:47:55.312870Z 'sec-ch-ua-platform-version' => [Object],
DEFAULT 2024-05-13T23:47:55.312874Z 'sec-ch-ua-wow64' => [Object],
DEFAULT 2024-05-13T23:47:55.312879Z 'sec-fetch-dest' => [Object],
DEFAULT 2024-05-13T23:47:55.312887Z 'sec-fetch-mode' => [Object],
DEFAULT 2024-05-13T23:47:55.312892Z 'sec-fetch-site' => [Object],
DEFAULT 2024-05-13T23:47:55.312896Z 'sec-fetch-user' => [Object],
DEFAULT 2024-05-13T23:47:55.312900Z 'traceparent' => [Object],
DEFAULT 2024-05-13T23:47:55.312904Z 'upgrade-insecure-requests' => [Object],
DEFAULT 2024-05-13T23:47:55.312907Z 'user-agent' => [Object],
DEFAULT 2024-05-13T23:47:55.312910Z 'x-client-data' => [Object],
DEFAULT 2024-05-13T23:47:55.312917Z 'x-cloud-trace-context' => [Object],
DEFAULT 2024-05-13T23:47:55.312921Z 'x-forwarded-for' => [Object],
DEFAULT 2024-05-13T23:47:55.312925Z 'x-forwarded-host' => [Object],
DEFAULT 2024-05-13T23:47:55.312929Z 'x-forwarded-port' => [Object],
DEFAULT 2024-05-13T23:47:55.312933Z 'x-forwarded-proto' => [Object]
DEFAULT 2024-05-13T23:47:55.312936Z },
DEFAULT 2024-05-13T23:47:55.312939Z [Symbol(headers map sorted)]: [
DEFAULT 2024-05-13T23:47:55.312942Z [Array], [Array], [Array],
DEFAULT 2024-05-13T23:47:55.312945Z [Array], [Array], [Array],
DEFAULT 2024-05-13T23:47:55.312948Z [Array], [Array], [Array],
DEFAULT 2024-05-13T23:47:55.312952Z [Array], [Array], [Array],
DEFAULT 2024-05-13T23:47:55.312954Z [Array], [Array], [Array],
DEFAULT 2024-05-13T23:47:55.312957Z [Array], [Array], [Array],
DEFAULT 2024-05-13T23:47:55.312960Z [Array], [Array], [Array],
DEFAULT 2024-05-13T23:47:55.312963Z [Array], [Array], [Array],
DEFAULT 2024-05-13T23:47:55.312967Z [Array], [Array], [Array],
DEFAULT 2024-05-13T23:47:55.312971Z [Array], [Array]
DEFAULT 2024-05-13T23:47:55.312975Z ]
DEFAULT 2024-05-13T23:47:55.312979Z },
DEFAULT 2024-05-13T23:47:55.312983Z [Symbol(internal request)]: {
DEFAULT 2024-05-13T23:47:55.312988Z cookies: RequestCookies { _parsed: [Map], _headers: [HeadersList] },
DEFAULT 2024-05-13T23:47:55.312990Z geo: {},
DEFAULT 2024-05-13T23:47:55.312993Z ip: undefined,
DEFAULT 2024-05-13T23:47:55.312996Z nextUrl: NextURL { [Symbol(NextURLInternal)]: [Object] },
DEFAULT 2024-05-13T23:47:55.313Z url: 'https://localhost:3000/auth/confirm?token_hash=pkce_633178ae4dbfcf566d5bba76adf2405c063a3713f07ced81d797a528&type=signup'
DEFAULT 2024-05-13T23:47:55.313002Z }
DEFAULT 2024-05-13T23:47:55.313006Z }
DEFAULT 2024-05-13T23:47:55.669131Z REDIRECTING TO:
DEFAULT 2024-05-13T23:47:55.669568Z NextURL {
DEFAULT 2024-05-13T23:47:55.669578Z [Symbol(NextURLInternal)]: {
DEFAULT 2024-05-13T23:47:55.669583Z url: URL {
DEFAULT 2024-05-13T23:47:55.669587Z href: 'https://localhost:3000/',
DEFAULT 2024-05-13T23:47:55.669592Z origin: 'https://localhost:3000',
DEFAULT 2024-05-13T23:47:55.669596Z protocol: 'https:',
DEFAULT 2024-05-13T23:47:55.669600Z username: '',
DEFAULT 2024-05-13T23:47:55.669604Z password: '',
DEFAULT 2024-05-13T23:47:55.669609Z host: 'localhost:3000',
DEFAULT 2024-05-13T23:47:55.669612Z hostname: 'localhost',
DEFAULT 2024-05-13T23:47:55.669616Z port: '3000',
DEFAULT 2024-05-13T23:47:55.669619Z pathname: '/',
DEFAULT 2024-05-13T23:47:55.669623Z search: '',
DEFAULT 2024-05-13T23:47:55.669627Z searchParams: URLSearchParams {},
DEFAULT 2024-05-13T23:47:55.669630Z hash: ''
DEFAULT 2024-05-13T23:47:55.669634Z },
DEFAULT 2024-05-13T23:47:55.669640Z options: { headers: [Object], nextConfig: undefined },
DEFAULT 2024-05-13T23:47:55.669644Z basePath: '',
DEFAULT 2024-05-13T23:47:55.669650Z domainLocale: undefined,
DEFAULT 2024-05-13T23:47:55.669655Z defaultLocale: undefined,
DEFAULT 2024-05-13T23:47:55.669658Z buildId: undefined,
DEFAULT 2024-05-13T23:47:55.669661Z locale: undefined,
DEFAULT 2024-05-13T23:47:55.669665Z trailingSlash: false
DEFAULT 2024-05-13T23:47:55.669669Z }
DEFAULT 2024-05-13T23:47:55.669674Z }

@RichardHruby
Copy link
Author

aims to fix #26279

@encima encima added the pr-opened A PR has been opened to resolve the issue label May 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation pr-opened A PR has been opened to resolve the issue
Projects
None yet
Development

No branches or pull requests

2 participants