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

安装

此库支持Node.js^16.15.0

入门

配置

设置填充环境变量。对于本地开发,您可以将其设置为.env文件。参见示例.

  # Find these in your Supabase project settings > API
PUBLIC_SUPABASE_URL=https://your-project.supabase.co
PUBLIC_SUPABASE_ANON_KEY=your-anon-key
  

设置Suabase客户端

首先创建一个db.ts文件,并实例化 supabaseClient

  import { createClient } from '@supabase/auth-helpers-sveltekit'
import { env } from '$env/dynamic/public'
// or use the static env
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';

export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY)
  

要确保在服务器和客户端上初始化了客户端,请在src/hooks.server.js中包含此文件。jssrc/hooks.client.js`:

  import '$lib/db'
  

同步页面存储

编辑+layout.svelte文件并设置客户端。

  <script>
  import { supabaseClient } from '$lib/db'
  import { invalidate } from '$app/navigation'
  import { onMount } from 'svelte'

  onMount(() => {
    const {
      data: { subscription },
    } = supabaseClient.auth.onAuthStateChange(() => {
      invalidate('supabase:auth')
    })

    return () => {
      subscription.unsubscribe()
    }
  })
</script>

<slot />
  

调用 invalidate('supabase:auth')时,使用getSupabase() 的每个PageLoadLayoutLoad都将更新。

如果某些数据在登录/注销时未更新,则可以返回到 invalidateAll()

向客户端发送会话

要使会话对UI(页面、布局)可用,请在根布局服务器加载函数中传递会话:

  import type { LayoutServerLoad } from './$types'
import { getServerSession } from '@supabase/auth-helpers-sveltekit'

export const load: LayoutServerLoad = async (event) => {
  return {
    session: await getServerSession(event),
  }
}
  

此外,如果使用invalidate('supabase:auth'),则可以创建布局加载函数:

  import type { LayoutLoad } from './$types'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'

export const load: LayoutLoad = async (event) => {
  const { session } = await getSupabase(event)
  return { session }
}
  

这会减少服务器调用,因为客户端自己管理会话。

键入

为了充分利用TypeScript及其智能感知,您应该将我们的类型导入到SveltKit项目附带的app.d.ts类型定义文件中。

  /// <reference types="@sveltejs/kit" />

// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
  interface Supabase {
    Database: import('./DatabaseDefinitions').Database
    SchemaName: 'public'
  }

  // interface Locals {}
  interface PageData {
    session: import('@supabase/supabase-js').Session | null
  }
  // interface Error {}
  // interface Platform {}
}
  

基本设置

现在,您可以通过检查 $page.data中的session对象来确定用户是否在客户端进行了身份验证。

  <script>
  import { page } from '$app/stores'
</script>

{#if !$page.data.session}
<h1>I am not logged in</h1>
{:else}
<h1>Welcome {$page.data.session.user.email}</h1>
<p>I am logged in!</p>
{/if}
  

使用RLS获取客户端数据

为了使行级别安全在客户端获取数据时正常工作,您需要确保从 $lib/db导入 { supabaseClient },并仅在 $page.data中定义了客户端会话后才运行查询:

  <script>
  import { supabaseClient } from '$lib/db'
  import { page } from '$app/stores'

  let loadedData = []
  async function loadData() {
    const { data } = await supabaseClient.from('test').select('*').limit(20)
    loadedData = data
  }

  $: if ($page.data.session) {
    loadData()
  }
</script>

{#if $page.data.session}
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
{/if}
  

使用RLS获取服务器端数据

  <script>
  /** @type {import('./$types').PageData} */
  export let data
  $: ({ user, tableData } = data)
</script>

<div>Protected content for {user.email}</div>
<pre>{JSON.stringify(tableData, null, 2)}</pre>
<pre>{JSON.stringify(user, null, 2)}</pre>
  

要使row-level security在服务器环境中工作,您需要使用 getSupabase帮助器来检查用户是否经过身份验证。助手需要 event 并返回 sessionsupabaseClient

  import type { PageLoad } from './$types'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
import { redirect } from '@sveltejs/kit'

export const load: PageLoad = async (event) => {
  const { session, supabaseClient } = await getSupabase(event)
  if (!session) {
    throw redirect(303, '/')
  }
  const { data: tableData } = await supabaseClient.from('test').select('*')

  return {
    user: session.user,
    tableData,
  }
}
  

保护API路由

包装API路由以检查用户是否具有有效会话。如果他们未登录,则会话为 null

  import type { RequestHandler } from './$types'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
import { json, redirect } from '@sveltejs/kit'

export const GET: RequestHandler = async (event) => {
  const { session, supabaseClient } = await getSupabase(event)
  if (!session) {
    throw redirect(303, '/')
  }
  const { data } = await supabaseClient.from('test').select('*')

  return json({ data })
}
  

如果您在没有有效会话cookie的情况下访问/api/protected-route,将得到303响应。

保护措施

包装操作以检查用户是否具有有效会话。如果他们未登录,则会话为 null

  import type { Actions } from './$types'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
import { error, invalid } from '@sveltejs/kit'

export const actions: Actions = {
  createPost: async (event) => {
    const { request } = event
    const { session, supabaseClient } = await getSupabase(event)
    if (!session) {
      // the user is not signed in
      throw error(403, { message: 'Unauthorized' })
    }
    // we are save, let the user create the post
    const formData = await request.formData()
    const content = formData.get('content')

    const { error: createPostError, data: newPost } = await supabaseClient
      .from('posts')
      .insert({ content })

    if (createPostError) {
      return invalid(500, {
        supabaseErrorMessage: createPostError.message,
      })
    }
    return {
      newPost,
    }
  },
}
  

如果您尝试提交带有操作的表单 ?/createPost如果没有有效的会话cookie,您将得到403错误响应。

保存和删除会话

  import type { Actions } from './$types'
import { invalid, redirect } from '@sveltejs/kit'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'

export const actions: Actions = {
  signin: async (event) => {
    const { request, cookies, url } = event
    const { session, supabaseClient } = await getSupabase(event)
    const formData = await request.formData()

    const email = formData.get('email') as string
    const password = formData.get('password') as string

    const { error } = await supabaseClient.auth.signInWithPassword({
      email,
      password,
    })

    if (error) {
      if (error instanceof AuthApiError && error.status === 400) {
        return invalid(400, {
          error: 'Invalid credentials.',
          values: {
            email,
          },
        })
      }
      return invalid(500, {
        error: 'Server error. Try again later.',
        values: {
          email,
        },
      })
    }

    throw redirect(303, '/dashboard')
  },

  signout: async (event) => {
    const { supabaseClient } = await getSupabase(event)
    await supabaseClient.auth.signOut()
    throw redirect(303, '/')
  },
}
  

保护多条路线

为了避免在每条路由中写入相同的身份验证逻辑,可以使用句柄钩子同时保护多条路由。

  import type { RequestHandler } from './$types'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
import { redirect, error } from '@sveltejs/kit'

export const handle: Handle = async ({ event, resolve }) => {
  // protect requests to all routes that start with /protected-routes
  if (event.url.pathname.startsWith('/protected-routes')) {
    const { session, supabaseClient } = await getSupabase(event)

    if (!session) {
      throw redirect(303, '/')
    }
  }

  // protect POST requests to all routes that start with /protected-posts
  if (event.url.pathname.startsWith('/protected-posts') && event.request.method === 'POST') {
    const { session, supabaseClient } = await getSupabase(event)

    if (!session) {
      throw error(303, '/')
    }
  }

  return resolve(event)
}
  

从0.7.x迁移到0.8

设置Supabase客户端

初始化客户端

设置挂钩

键入

withPageAuth

withApiAuth

从0.6.11及以下迁移到0.7.0

此库的最新0.7.0版本中有许多突破性的更改。

环境变量前缀

环境变量前缀现在是PUBLIC_而不是VITE_(例如,VITE_SUPABASE_URL现在是BUBLIC_SUPABASE_URL)。

设置Supabase客户端

初始化客户端

设置挂钩

键入

检查客户端上的用户

withPageAuth

withApiAuth

其他链接