Skip to content

Commit a4561f9

Browse files
fix(auth): eliminate redundant Supabase Auth API calls to prevent rate limiting
Removed duplicate getUser() call in proxy.ts middleware — updateSession() already validates the token server-side, so the second call was unnecessary. Also replaced getUser() with getSession() (local cookie read) in OAuth callback, and deleted the unused legacy lib/supabase/middleware.ts. Cuts auth API calls from 2-3 to 1 per request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6864901 commit a4561f9

4 files changed

Lines changed: 22 additions & 83 deletions

File tree

app/api/auth/callback/route.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@ export async function GET(request: Request) {
2323
const { error } = await supabase.auth.exchangeCodeForSession(code)
2424

2525
if (!error) {
26-
// Get the user to determine the correct dashboard redirect
27-
const { data: { user } } = await supabase.auth.getUser()
26+
// Get session from cookie (no network call) — exchangeCodeForSession already
27+
// validated the token and established the session above.
28+
const { data: { session } } = await supabase.auth.getSession()
2829

29-
if (user) {
30-
// Get user role from JWT claims for routing
31-
const { data: { session } } = await supabase.auth.getSession()
30+
if (session?.user) {
3231
let userRole = 'student'
3332

3433
if (session?.access_token) {

lib/supabase/middleware.ts

Lines changed: 0 additions & 61 deletions
This file was deleted.

lib/supabase/proxy.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createServerClient } from '@supabase/ssr'
22
import { NextResponse, type NextRequest } from 'next/server'
3+
import type { User } from '@supabase/supabase-js'
34

45
// Derive root domain for cross-subdomain cookie sharing
56
function getCookieDomain(): string | undefined {
@@ -10,7 +11,7 @@ function getCookieDomain(): string | undefined {
1011
return `.${domain}`
1112
}
1213

13-
export async function updateSession(request: NextRequest) {
14+
export async function updateSession(request: NextRequest): Promise<{ response: NextResponse; user: User | null }> {
1415
let supabaseResponse = NextResponse.next({
1516
request,
1617
})
@@ -49,8 +50,11 @@ export async function updateSession(request: NextRequest) {
4950

5051
// IMPORTANT: DO NOT REMOVE auth.getUser()
5152
// A simple mistake could make it very hard to debug issues with users being randomly logged out.
53+
// Returns the user so callers don't need a second getUser() network call.
54+
let user: User | null = null
5255
try {
53-
await supabase.auth.getUser()
56+
const { data } = await supabase.auth.getUser()
57+
user = data.user
5458
} catch (e: any) {
5559
// If refresh token is invalid/expired, clear auth cookies so we stop retrying
5660
if (e?.code === 'refresh_token_not_found' || e?.message?.includes('Refresh Token Not Found')) {
@@ -64,5 +68,5 @@ export async function updateSession(request: NextRequest) {
6468
}
6569
}
6670

67-
return supabaseResponse
71+
return { response: supabaseResponse, user }
6872
}

proxy.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -215,23 +215,25 @@ export default async function proxy(request: NextRequest) {
215215
normalizedPath === route || normalizedPath.startsWith(route + '/')
216216
)
217217

218-
// Update session
219-
const supabaseResponse = await updateSession(request)
218+
// Update session — validates token server-side (1 auth API call) and returns the user.
219+
// Re-using the user here avoids a second getUser() network call.
220+
const { response: supabaseResponse, user } = await updateSession(request)
220221

221222
// Set tenant ID header on the response for downstream server components
222223
intlResponse.headers.set('x-tenant-id', tenantId)
223224
supabaseResponse.headers.set('x-tenant-id', tenantId)
224225

225-
// Create client to check session — writes refreshed cookies to supabaseResponse
226+
// Single Supabase client used for the rest of this middleware run.
227+
// getSession() reads from cookie (no network call) — safe because updateSession
228+
// already validated the token server-side above.
226229
const supabase = createServerClient(
227230
process.env.NEXT_PUBLIC_SUPABASE_URL!,
228231
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY!,
229232
{
230233
cookies: {
231234
getAll() { return request.cookies.getAll() },
232235
setAll(cookiesToSet) {
233-
// Propagate refreshed JWT cookies to the response so tenant switch
234-
// takes effect immediately (fixes stale get_tenant_id() in RLS)
236+
// Propagate any cookie writes (e.g. from refreshSession) to the response
235237
const cookieDomain = (() => {
236238
const d = process.env.NEXT_PUBLIC_PLATFORM_DOMAIN?.split(':')[0]
237239
if (!d || d === 'localhost' || d === '127.0.0.1') return undefined
@@ -249,19 +251,14 @@ export default async function proxy(request: NextRequest) {
249251
}
250252
)
251253

252-
let user = null
253254
let session = null
254-
try {
255-
const { data: userData } = await supabase.auth.getUser()
256-
user = userData.user
257-
if (user) {
255+
if (user) {
256+
try {
258257
const { data: sessionData } = await supabase.auth.getSession()
259258
session = sessionData.session
259+
} catch {
260+
session = null
260261
}
261-
} catch {
262-
// Invalid/expired JWT — treat as unauthenticated
263-
user = null
264-
session = null
265262
}
266263

267264
let userRole: 'student' | 'teacher' | 'admin' = 'student'

0 commit comments

Comments
 (0)