Skip to content

Commit 2330213

Browse files
committed
fix: resolve authentication issues in production - replace deprecated auth-helpers with modern SSR - fix PKCE flow to use server-side instead of client-side - update environment variable handling for production vs development - fix OAuth callback route and component imports - remove @supabase/auth-helpers-nextjs dependency - resolves 401 Unauthorized PKCE errors
1 parent 7779501 commit 2330213

File tree

12 files changed

+207
-138
lines changed

12 files changed

+207
-138
lines changed

env.example

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1-
# Local Supabase Configuration
1+
# Environment Configuration
2+
NODE_ENV=development
3+
4+
# Site URL Configuration
25
NEXT_PUBLIC_SITE_URL=http://localhost:3000
6+
7+
# Local Supabase Configuration (Development)
38
NEXT_PUBLIC_SUPABASE_URL=http://localhost:5430
49
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
5-
SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key
10+
NEXT_PUBLIC_SUPABASE_REALTIMEURL=http://localhost:5430
11+
NEXT_PUBLIC_SERVICE_ROLE_KEY=your-supabase-service-role-key
12+
13+
# Production Supabase Configuration (Production)
14+
SUPABASE_URL=https://your-project.supabase.co
15+
SUPABASE_ANON_KEY=your-production-anon-key
16+
SUPABASE_ROLE_KEY=your-production-service-role-key
17+
SUPABASE_DATABASE_URL=postgresql://postgres:[password]@db.[project-ref].supabase.co:5432/postgres
18+
SUPABASE_PROJECT_ID=your-project-id
619

720
# Database Configuration
821
NEXT_PUBLIC_DATABASE_URL=postgres://postgres:postgres@localhost:54322/postgres

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
"@radix-ui/react-toast": "^1.1.5",
4444
"@radix-ui/react-tooltip": "^1.0.7",
4545
"@stripe/stripe-js": "^4.0.0",
46-
"@supabase/auth-helpers-nextjs": "^0.10.0",
4746
"@supabase/realtime-js": "^2.10.2",
4847
"@supabase/ssr": "^0.3.0",
4948
"@supabase/supabase-js": "^2.44.3",

src/app/api/auth/callback/route.ts

Lines changed: 56 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,61 @@
1-
// import { NextRequest, NextResponse } from 'next/server';
2-
// import { createClient } from '@/utils/server';
3-
4-
// export async function POST(request: NextRequest) {
5-
// try {
6-
// console.log('Auth callback - Starting POST request');
7-
// const supabase = await createClient();
8-
// const { searchParams } = new URL(request.url);
9-
// const code = searchParams.get('code');
10-
// const error = searchParams.get('error') || searchParams.get('error_description');
11-
12-
// console.log('Auth callback - Code:', code ? 'present' : 'missing');
13-
// console.log('Auth callback - Error:', error || 'none');
14-
15-
// if (error) {
16-
// console.error('Auth callback error:', error);
17-
// return NextResponse.redirect(
18-
// new URL(`/login?error=${encodeURIComponent(error)}`, request.url)
19-
// );
20-
// }
21-
22-
// if (code) {
23-
// console.log('Auth callback - Attempting to exchange code for session');
24-
// const { data, error: exchangeError } = await supabase.auth.exchangeCodeForSession(code);
25-
26-
// console.log('Auth callback - Exchange result:', {
27-
// success: !exchangeError,
28-
// error: exchangeError?.message,
29-
// user: data?.user ? 'present' : 'missing',
30-
// });
31-
32-
// if (exchangeError) {
33-
// console.error('Code exchange error:', exchangeError);
34-
// return NextResponse.redirect(new URL('/login?error=auth_failed', request.url));
35-
// }
36-
37-
// if (data?.user) {
38-
// console.log('Auth callback - Successfully authenticated user:', data.user.email);
39-
// // Successful authentication
40-
// return NextResponse.redirect(new URL('/dashboard', request.url));
41-
// } else {
42-
// console.error('Auth callback - No user data after successful exchange');
43-
// return NextResponse.redirect(new URL('/login?error=no_user_data', request.url));
44-
// }
45-
// }
46-
47-
// // No code or error provided
48-
// console.error('Auth callback - No code provided');
49-
// return NextResponse.redirect(new URL('/login?error=invalid_callback', request.url));
50-
// } catch (error) {
51-
// console.error('Auth callback - Unexpected error:', error);
52-
// return NextResponse.redirect(new URL('/login?error=server_error', request.url));
53-
// }
54-
// }
55-
56-
// export async function GET(request: NextRequest) {
57-
// // Handle GET requests (for direct browser navigation)
58-
// return POST(request);
59-
//
60-
61-
import { NextResponse } from 'next/server';
62-
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
63-
import { cookies } from 'next/headers';
64-
65-
export async function GET(request: Request) {
66-
const url = new URL(request.url);
67-
const code = url.searchParams.get('code');
68-
const error = url.searchParams.get('error');
69-
70-
if (error) {
71-
return NextResponse.redirect(new URL(`/login?error=${error}`, request.url));
72-
}
73-
74-
if (code) {
75-
const supabase = createRouteHandlerClient({ cookies });
76-
await supabase.auth.exchangeCodeForSession(code);
77-
return NextResponse.redirect(new URL('/dashboard', request.url));
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { createClient } from '@/utils/server';
3+
4+
export async function POST(request: NextRequest) {
5+
try {
6+
console.log('Auth callback - Starting POST request');
7+
const supabase = await createClient();
8+
const { searchParams } = new URL(request.url);
9+
const code = searchParams.get('code');
10+
const error = searchParams.get('error') || searchParams.get('error_description');
11+
12+
console.log('Auth callback - Code:', code ? 'present' : 'missing');
13+
console.log('Auth callback - Error:', error || 'none');
14+
15+
if (error) {
16+
console.error('Auth callback error:', error);
17+
return NextResponse.redirect(
18+
new URL(`/login?error=${encodeURIComponent(error)}`, request.url)
19+
);
20+
}
21+
22+
if (code) {
23+
console.log('Auth callback - Attempting to exchange code for session');
24+
const { data, error: exchangeError } = await supabase.auth.exchangeCodeForSession(code);
25+
26+
console.log('Auth callback - Exchange result:', {
27+
success: !exchangeError,
28+
error: exchangeError?.message,
29+
user: data?.user ? 'present' : 'missing',
30+
});
31+
32+
if (exchangeError) {
33+
console.error('Code exchange error:', exchangeError);
34+
return NextResponse.redirect(new URL('/login?error=auth_failed', request.url));
35+
}
36+
37+
if (data?.user) {
38+
console.log('Auth callback - Successfully authenticated user:', data.user.email);
39+
// Successful authentication
40+
return NextResponse.redirect(new URL('/dashboard', request.url));
41+
} else {
42+
console.error('Auth callback - No user data after successful exchange');
43+
return NextResponse.redirect(new URL('/login?error=no_user_data', request.url));
44+
}
45+
}
46+
47+
// No code or error provided
48+
console.error('Auth callback - No code provided');
49+
return NextResponse.redirect(new URL('/login?error=invalid_callback', request.url));
50+
} catch (error) {
51+
console.error('Auth callback - Unexpected error:', error);
52+
return NextResponse.redirect(new URL('/login?error=server_error', request.url));
7853
}
54+
}
7955

80-
return NextResponse.redirect(new URL('/login?error=missing_code', request.url));
56+
export async function GET(request: NextRequest) {
57+
// Handle GET requests (for direct browser navigation)
58+
return POST(request);
8159
}
8260

8361

src/app/api/auth/test/route.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { NextResponse } from 'next/server';
2+
import { createClient } from '@/utils/server';
3+
4+
export async function GET() {
5+
try {
6+
console.log('Testing Supabase connection...');
7+
8+
const supabase = await createClient();
9+
10+
// Test basic connection
11+
const { data: { user }, error: userError } = await supabase.auth.getUser();
12+
13+
if (userError) {
14+
console.error('Auth error:', userError);
15+
return NextResponse.json({
16+
success: false,
17+
error: userError.message,
18+
type: 'auth_error'
19+
}, { status: 500 });
20+
}
21+
22+
// Test database connection
23+
const { data: dbData, error: dbError } = await supabase
24+
.from('users')
25+
.select('count')
26+
.limit(1);
27+
28+
if (dbError) {
29+
console.error('Database error:', dbError);
30+
return NextResponse.json({
31+
success: false,
32+
error: dbError.message,
33+
type: 'database_error'
34+
}, { status: 500 });
35+
}
36+
37+
return NextResponse.json({
38+
success: true,
39+
message: 'Supabase connection successful',
40+
user: user ? 'authenticated' : 'not_authenticated',
41+
database: 'connected'
42+
});
43+
44+
} catch (error) {
45+
console.error('Test route error:', error);
46+
return NextResponse.json({
47+
success: false,
48+
error: error instanceof Error ? error.message : 'Unknown error',
49+
type: 'general_error'
50+
}, { status: 500 });
51+
}
52+
}

src/components/features/auth/forgot-password/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useState } from 'react';
44
import { Button } from '@/components/ui/button';
55
import { Input } from '@/components/ui/input';
66
import { toast } from 'sonner';
7-
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
7+
import { createClient } from '@/utils/client';
88
import Link from 'next/link';
99
import Image from 'next/image';
1010

@@ -15,7 +15,7 @@ export default function ForgotPasswordPage() {
1515
const handleSubmit = async (e: React.FormEvent) => {
1616
e.preventDefault();
1717
setLoading(true);
18-
const supabase = createClientComponentClient();
18+
const supabase = createClient();
1919
const { error } = await supabase.auth.resetPasswordForEmail(email, {
2020
redirectTo: `${window.location.origin}/reset-password`,
2121
});

src/components/features/auth/reset-password/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
55
import { Button } from '@/components/ui/button';
66
import { Input } from '@/components/ui/input';
77
import { toast } from 'sonner';
8-
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
8+
import { createClient } from '@/utils/client';
99
import Link from 'next/link';
1010
import Image from 'next/image';
1111

@@ -17,7 +17,7 @@ export default function ResetPasswordPage() {
1717
const handleSubmit = async (e: React.FormEvent) => {
1818
e.preventDefault();
1919
setLoading(true);
20-
const supabase = createClientComponentClient();
20+
const supabase = createClient();
2121
const { error } = await supabase.auth.updateUser({ password });
2222
setLoading(false);
2323
if (error) {

src/components/features/side-bar/components/side-bar.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import * as React from 'react';
2-
import { cookies } from 'next/headers';
3-
import {
4-
getCollaboratingWorkspaces,
5-
getFolders,
6-
getPrivateWorkspaces,
7-
getSharedWorkspaces,
8-
getUserSubscriptionStatus,
9-
} from '@/lib/supabase/queries';
102
import { twMerge } from 'tailwind-merge';
113
import WorkSpaceDropdown from '../../main/workspace';
124
import PlanUsage from './plan-usage';
135
import NativeNavigation from './native-navigation';
146
import { ScrollArea } from '@/components/ui/scroll-area';
157
import FoldersDropdownList from './folders-dropdown-list';
168
import UserCard from './user-card';
17-
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
9+
import { createClient } from '@/utils/server';
10+
import {
11+
getCollaboratingWorkspaces,
12+
getFolders,
13+
getPrivateWorkspaces,
14+
getSharedWorkspaces,
15+
getUserSubscriptionStatus,
16+
} from '@/lib/supabase/queries';
1817

1918
interface ISidebarProps {
2019
params: Promise<{ workspaceId: string }>;
@@ -25,7 +24,7 @@ const Sidebar = async ({ params, className }: ISidebarProps) => {
2524
// Await the params object to fix Next.js dynamic API issue
2625
const { workspaceId } = await params;
2726

28-
const supabase = createServerComponentClient({ cookies });
27+
const supabase = await createClient();
2928

3029
//user
3130
const {

src/components/features/side-bar/components/user-card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import CypressProfileIcon from '../../../icons/cypressProfileIcon';
66
import { ModeToggle, LogoutButton } from '../../../global-components';
77
import { LogOut } from 'lucide-react';
88
import { Loader } from '@/components/global-components';
9-
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
9+
import { createClient } from '@/utils/server';
1010
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
1111
import { postgrestGet } from '@/utils/client';
1212

@@ -15,7 +15,7 @@ interface UserCardProps {
1515
}
1616

1717
const UserCard = async ({ subscription }: UserCardProps) => {
18-
const supabase = createServerComponentClient({ cookies });
18+
const supabase = await createClient();
1919
const {
2020
data: { user },
2121
} = await supabase.auth.getUser();

src/lib/server-action/auth-action.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,19 @@ export async function actionLoginUser({ email, password }: z.infer<typeof FormSc
8080
export async function socialLogin(provider: 'google' | 'github') {
8181
const supabase = await createClient();
8282
console.log('\n\nprovider', provider);
83+
84+
// Use server-side OAuth flow to avoid PKCE issues
8385
const { data, error } = await supabase.auth.signInWithOAuth({
8486
provider,
8587
options: {
8688
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/api/auth/callback`,
89+
// Force server-side flow to avoid PKCE issues
90+
flowType: 'pkce',
91+
// Ensure we're using the correct site URL
92+
queryParams: {
93+
access_type: 'offline',
94+
prompt: 'consent',
95+
},
8796
},
8897
});
8998

src/utils/client.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,47 @@
11
import { createBrowserClient } from '@supabase/ssr';
22

3+
// Determine the correct Supabase URL and key based on environment
4+
const getSupabaseConfig = () => {
5+
if (typeof window !== 'undefined') {
6+
// Client-side: always use NEXT_PUBLIC variables
7+
return {
8+
url: process.env.NEXT_PUBLIC_SUPABASE_URL!,
9+
anonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
10+
};
11+
}
12+
13+
// Server-side: use environment-specific variables
14+
if (process.env.NODE_ENV === 'production') {
15+
return {
16+
url: process.env.SUPABASE_URL!,
17+
anonKey: process.env.SUPABASE_ANON_KEY!,
18+
};
19+
}
20+
21+
return {
22+
url: process.env.NEXT_PUBLIC_SUPABASE_URL!,
23+
anonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
24+
};
25+
};
26+
27+
const config = getSupabaseConfig();
28+
329
export const client = createBrowserClient(
4-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
5-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
30+
config.url,
31+
config.anonKey,
632
{
733
realtime: {
834
params: {
935
eventsPerSecond: 2,
1036
},
1137
},
38+
auth: {
39+
// Ensure proper auth flow
40+
flowType: 'pkce',
41+
autoRefreshToken: true,
42+
persistSession: true,
43+
detectSessionInUrl: true,
44+
},
1245
}
1346
);
1447

0 commit comments

Comments
 (0)