该子模块为在Remix应用程序中实现用户身份验证提供了方便的帮助。

安装Remix助手库

设置环境变量

在项目的应用设置->API中检索项目URL和匿名密钥以设置以下环境变量。对于本地开发,您可以将其设置为.env文件。参见示例.

  SUPABASE_URL=YOUR_SUPABASE_URL
SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
  

加载器

Action

会话和用户

你可以通过使用getSession函数检查用户的会话来确定用户是否被认证。

  const {
  data: { session },
} = await supabaseClient.auth.getSession()
  

The session contains a user property.

  const user = session?.user
  

这是访问登录用户的推荐方法。也有一个getUser()函数,但如果会话已经过期,它不会刷新。

客户端

身份认证

现在,认证是基于cookie的,用户可以通过操作在服务器端签入和签出。

给出这个Remix <Form />组件。

  <Form method="post">
  <input type="text" name="email" />
  <input type="password" name="password" />
  <button type="submit">Go!</button>
</Form>
  

注册

登录

退出登录

订阅实时事件

  import { createBrowserClient } from '@supabase/auth-helpers-remix'
import { useState, useEffect } from 'react'

export default function SubscribeToRealtime() {
  const [data, setData] = useState([])

  useEffect(() => {
    const supabaseClient = createBrowserClient(
      window.env.SUPABASE_URL,
      window.env.SUPABASE_ANON_KEY
    )
    const channel = supabaseClient
      .channel('test')
      .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'test' }, (payload) => {
        setData((data) => [...data, payload.new])
      })
      .subscribe()

    return () => {
      supabaseClient.removeChannel(channel)
    }
  }, [session])

  return <pre>{JSON.stringify({ data }, null, 2)}</pre>
}
  

注意:window.env不是由Remix自动填充的。请看上面的 客户端说明来配置它。

在这个例子中,我们要监听test表的INSERT事件。只要Supabase的test表有新行,我们的用户界面就会自动更新新数据。

在实时事件中合并服务器和客户端状态

  import { json, LoaderFunction } from '@remix-run/node';
import { useLoaderData, useNavigate } from '@remix-run/react';
import {
  createServerClient,
  createBrowserClient
} from '@supabase/auth-helpers-remix';
import { useEffect } from 'react';
import { Database } from '../../db_types';

// this route demonstrates how to subscribe to realtime updates
// and synchronize data between server and client
export const loader: LoaderFunction = async ({
  request
}: {
  request: Request;
}) => {
  const response = new Response();
  const supabaseClient = createServerClient<Database>(
    process.env.SUPABASE_URL!,
    process.env.SUPABASE_ANON_KEY!,
    { request, response }
  );

  const {
    data: { session }
  } = await supabaseClient.auth.getSession();

  const { data, error } = await supabaseClient.from('test').select('*');

  if (error) {
    throw error;
  }

  // in order for the set-cookie header to be set,
  // headers must be returned as part of the loader response
  return json(
    { data, session },
    {
      headers: response.headers
    }
  );
};

export default function SubscribeToRealtime() {
  const { data, session } = useLoaderData();
  const navigate = useNavigate();

  useEffect(() => {
    // Note: window.env is not automatically populated by Remix
    // Check out the [example in this repo](../root.tsx) or
    // [Remix docs](https://remix.run/docs/en/v1/guides/envvars#browser-environment-variables) for more info
    const supabaseClient = createBrowserClient<Database>(
      window.env.SUPABASE_URL,
      window.env.SUPABASE_ANON_KEY
    );
    // make sure you have enabled `Replication` for your table to receive realtime events
    // https://supabase.com/docs/app/development_guide/database/replication
    const channel = supabaseClient
      .channel('test')
      .on(
        'postgres_changes',
        { event: '*', schema: 'public', table: 'test' },
        (payload: any) => {
          // you could manually merge the `payload` with `data` here
          // the `navigate` trick below causes all active loaders to be called again
          // this handles inserts, updates and deletes, keeping everything in sync
          // which feels more remix-y than manually merging state
          // https://sergiodxa.com/articles/automatic-revalidation-in-remix
          navigate('.', { replace: true });
        }
      )
      .subscribe();

    return () => {
      supabaseClient.removeChannel(channel);
    };
  }, [session]);

  return (
    <div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}>
      <pre>{JSON.stringify({ data }, null, 2)}</pre>
    </div>
  );
}
  

注意:window.env不是由Remix自动填充的。请看上面的 客户端说明来配置它。

使用TypeScript的用法

你可以将用Supabase CLI生成的类型传递给createServerClientcreateBrowserClient函数以获得增强的类型安全和自动完成。

服务器端

  import { createServerClient } from '@supabase/auth-helpers-remix'
import { Database } from '../../db_types'

export const loader = async ({ request }) => {
  const response = new Response()

  const supabaseClient = createServerClient<Database>(
    process.env.SUPABASE_URL,
    process.env.SUPABASE_ANON_KEY,
    { request, response }
  )
}
  

客户端

  import { createBrowserClient } from '@supabase/auth-helpers-remix'
import { Database } from '../../db_types'

const supabaseClient = createBrowserClient<Database>(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
)
  

使用provider_token向OAuth APIs获取服务器端数据

当使用第三方认证供应商时,会话是由一个额外的provider_token字段发起的,该字段被持久地保存在认证cookie中,可以在会话对象中访问。provider_token可以用来代表登录的用户向OAuth提供商的API端点发出API请求。

  import { json, LoaderFunction, redirect } from '@remix-run/node'; // change this import to whatever runtime you are using
import { useLoaderData } from '@remix-run/react';
import { createServerClient, User } from '@supabase/auth-helpers-remix';
import { Database } from '../../db_types';

export const loader: LoaderFunction = async ({
  request
}: {
  request: Request;
}) => {
  const response = new Response();

  const supabaseClient = createServerClient<Database>(
    process.env.SUPABASE_URL!,
    process.env.SUPABASE_ANON_KEY!,
    { request, response }
  );

  const {
    data: { session }
  } = await supabaseClient.auth.getSession();

  if (!session) {
    // there is no session, therefore, we are redirecting
    // to the landing page. we still need to return
    // response.headers to attach the set-cookie header
    return redirect('/', {
      headers: response.headers
    });
  }

  // Retrieve provider_token & logged in user's third-party id from metadata
  const { provider_token, user } = session;
  const userId = user.user_metadata.user_name;

  const allRepos = await (
    await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, {
      method: 'GET',
      headers: {
        Authorization: `token ${provider_token}`
      }
    })
  ).json();

  // in order for the set-cookie header to be set,
  // headers must be returned as part of the loader response
  return json(
    { user, allRepos },
    {
      headers: response.headers
    }
  );
};

export default function ProtectedPage() {
  // by fetching the user in the loader, we ensure it is available
  // for first SSR render - no flashing of incorrect state
  const { user, allRepos } = useLoaderData<{ user: User; allRepos: any }>();

  return <pre>{JSON.stringify({ user, allRepos }, null, 2)}</pre>;
}