英文:
How can I password protect every page in NextJS and Supabase, using the Supabase default auth helpers and UI?
问题
我正在尝试在我的NextJS项目中向每个页面添加用户身份验证(页面,而不是应用程序)。这个教程非常有帮助(也正是我想做的) - https://alexsidorenko.com/blog/next-js-protected-routes/ - 但我在将Supabase的默认身份验证界面和功能集成到该模型(https://supabase.com/docs/guides/auth/auth-helpers/nextjs)方面遇到了困难。
我的基本目标是将身份验证分支移到_app.tsx,而不是每个页面上:
// _app.tsx
import { useEffect, useState } from "react";
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs';
import { SessionContextProvider, useUser, useSession, useSupabaseClient, Session } from '@supabase/auth-helpers-react';
import { Auth, ThemeSupa } from '@supabase/auth-ui-react';
import { AppProps } from 'next/app';
import { UserContext } from "@components/user";
function MyApp({ Component, pageProps }: AppProps<{ initialSession: Session }>) {
const [supabase] = useState(() => createBrowserSupabaseClient());
const session = useSession();
const user = useUser();
console.log("session:" + session);
console.log("user:" + user);
useEffect(() => {
if (pageProps.protected) {
return <Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />;
}
}, []);
return (
<SessionContextProvider supabaseClient={supabase} session={session} initialSession={pageProps.initialSession}>
<Component {...pageProps} />
</SessionContextProvider>
);
}
export default MyApp;
我想要保护的页面(例如,首页)如下所示:
// index.tsx
import Account from "@components/account";
const Home = () => {
return (
<div>
<Account session={session} />
</div>
);
};
export async function getStaticProps(context) {
return {
props: {
protected: true,
},
};
}
export default Home;
然后在首页页面中包含的Account组件是Supabase的开箱即用的个人资料面板,尽管它可以是任何内容:
// @components/account.tsx
import { useState, useEffect } from 'react';
import { useUser, useSupabaseClient, Session } from '@supabase/auth-helpers-react';
import { Database } from '@utils/database.types';
type Profiles = Database['public']['Tables']['profiles']['Row'];
export default function Account({ session }: { session: Session }) {
const supabase = useSupabaseClient<Database>();
const user = useUser();
const [loading, setLoading] = useState(true);
const [username, setUsername] = useState<Profiles['username']>(null);
useEffect(() => {
getProfile();
}, [session]);
async function getProfile() {
try {
setLoading(true);
if (!user) throw new Error('No user');
let { data, error, status } = await supabase
.from('profiles')
.select(`username`)
.eq('id', user.id)
.single();
if (error && status !== 406) {
throw error;
}
if (data) {
setUsername(data.username);
}
} catch (error) {
alert('Error loading user data!');
console.log(error);
} finally {
setLoading(false);
}
}
async function updateProfile({
username,
}: {
username: Profiles['username'];
}) {
try {
setLoading(true);
if (!user) throw an Error('No user');
const updates = {
id: user.id,
username,
updated_at: new Date().toISOString(),
};
let { error } = await supabase.from('profiles').upsert(updates);
if (error) throw error;
alert('Profile updated!');
} catch (error) {
alert('Error updating the data!');
console.log(error);
} finally {
setLoading(false);
}
}
return (
<div>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Username</label>
<input id="username" type="text" value={username || ''} onChange={(e) => setUsername(e.target.value)} />
</div>
<div>
<button onClick={() => updateProfile({ username })} disabled={loading} >
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
</div>
);
}
我认为我对受保护路由和Supabase的会话和用户使用之间的关系有基本的误解。希望这可以帮助你理解如何在Next.js项目中实现用户身份验证和保护路由。
英文:
I'm trying to add user authentication to every page in my NextJS project (pages, not app.) This tutorial was very helpful (and is exactly what I want to do) - <https://alexsidorenko.com/blog/next-js-protected-routes/> - but I'm having trouble integrating Supabase's default auth UI and capabilities into that model (<https://supabase.com/docs/guides/auth/auth-helpers/nextjs>).
My basic goal is to move authentication branching into _app.tsx, rather than on each page:
// _app.tsx
import { useEffect, useState } from "react";
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider, useUser, useSession, useSupabaseClient, Session } from '@supabase/auth-helpers-react'
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
import { AppProps } from 'next/app'
import { UserContext } from "@components/user"
function MyApp({Component, pageProps}: AppProps<{ initialSession: Session }>) {
const [supabase] = useState(() => createBrowserSupabaseClient())
const session = useSession()
const user = useUser()
console.log("session:" + session);
console.log("user:" + user);
useEffect(() => {
if (
pageProps.protected
) {
return <Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
}
}, [])
return (
<SessionContextProvider supabaseClient={supabase} session={session} initialSession={pageProps.initialSession}>
<Component {...pageProps} />
</SessionContextProvider>
)
}
export default MyApp
A page I want to protect (for example, the index page) looks like this:
// index.tsx
import Account from "@components/account";
const Home = () => {
return (
<div>
<Account session={session} />
</div>
)
}
export async function getStaticProps(context) {
return {
props: {
protected: true,
},
}
}
export default Home
And then the Account component that's included on the index page is the Supabase out of the box profile panel, although it could be any content:
// @components/account.tsx
import { useState, useEffect } from 'react'
import { useUser, useSupabaseClient, Session } from '@supabase/auth-helpers-react'
import { Database } from '@utils/database.types'
type Profiles = Database['public']['Tables']['profiles']['Row']
export default function Account({ session }: { session: Session }) {
const supabase = useSupabaseClient<Database>()
const user = useUser()
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState<Profiles['username']>(null)
useEffect(() => {
getProfile()
}, [session])
async function getProfile() {
try {
setLoading(true)
if (!user) throw new Error('No user')
let { data, error, status } = await supabase
.from('profiles')
.select(`username`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
}
} catch (error) {
alert('Error loading user data!')
console.log(error)
} finally {
setLoading(false)
}
}
async function updateProfile({
username,
}: {
username: Profiles['username']
}) {
try {
setLoading(true)
if (!user) throw new Error('No user')
const updates = {
id: user.id,
username,
updated_at: new Date().toISOString(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) throw error
alert('Profile updated!')
} catch (error) {
alert('Error updating the data!')
console.log(error)
} finally {
setLoading(false)
}
}
return (
<div>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Username</label>
<input id="username" type="text" value={username || ''} onChange={(e) => setUsername(e.target.value)} />
</div>
<div>
<button onClick={() => updateProfile({ username })} disabled={loading} >
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
</div>
)
}
I think I have a fundamental misunderstanding of the relationship between protected routes and Supabase's use of session and user.
Any help would be very much appreciated.
答案1
得分: 1
我建议在这种情况下使用Next.js中间件:https://supabase.com/docs/guides/auth/auth-helpers/nextjs#auth-with-nextjs-middleware
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(req: NextRequest) {
// 我们需要创建一个响应并将其交给Supabase客户端,以便能够修改响应标头。
const res = NextResponse.next()
// 创建经过身份验证的Supabase客户端。
const supabase = createMiddlewareSupabaseClient({ req, res })
// 检查是否有会话
const {
data: { session },
} = await supabase.auth.getSession()
// 检查身份验证条件
if (session?.user.email?.endsWith('@gmail.com')) {
// 身份验证成功,将请求转发到受保护的路由。
return res
}
// 未满足身份验证条件,重定向到主页。
const redirectUrl = req.nextUrl.clone()
redirectUrl.pathname = '/'
redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname)
return NextResponse.redirect(redirectUrl)
}
export const config = {
matcher: '/middleware-protected/:path*',
}
英文:
I'd recommend using Next.js middleware for this: https://supabase.com/docs/guides/auth/auth-helpers/nextjs#auth-with-nextjs-middleware
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(req: NextRequest) {
// We need to create a response and hand it to the supabase client to be able to modify the response headers.
const res = NextResponse.next()
// Create authenticated Supabase Client.
const supabase = createMiddlewareSupabaseClient({ req, res })
// Check if we have a session
const {
data: { session },
} = await supabase.auth.getSession()
// Check auth condition
if (session?.user.email?.endsWith('@gmail.com')) {
// Authentication successful, forward request to protected route.
return res
}
// Auth condition not met, redirect to home page.
const redirectUrl = req.nextUrl.clone()
redirectUrl.pathname = '/'
redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname)
return NextResponse.redirect(redirectUrl)
}
export const config = {
matcher: '/middleware-protected/:path*',
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论