Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Migrate to @supabase/ssr #357

Merged
merged 26 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
74624c1
refactor: migrate to @supabase/ssr
felixgabler Apr 27, 2024
6123971
refactor: use clientOptions from @supabase/ssr by default
felixgabler Apr 27, 2024
4dcea96
fix: use h3 getCookie instead of useCookie in server plugin
felixgabler Apr 27, 2024
cdde49f
chore: update supabase-js to fix warning
felixgabler Apr 27, 2024
60384af
refactor: fix tsc & lint issues
felixgabler Apr 27, 2024
b768da8
fix: unwrap proxy object from cookie to enable message posting
felixgabler Apr 28, 2024
7f468d3
refactor(composables): useSupabaseuser and useSupabaseSession async
larbish Apr 30, 2024
1b0209b
fix(lint): useless import
larbish Apr 30, 2024
988c9a5
chore(app): remove logs
larbish Apr 30, 2024
e5062b1
refactor: fix ts errors
felixgabler Apr 30, 2024
b4b6975
fix(serverSupabaseUser): getuser instead of getSession
larbish May 2, 2024
f05024c
chore(module): optimize cookie dep
larbish May 2, 2024
660b811
Merge branch 'main' into pr/357
larbish May 2, 2024
3fd5784
Merge branch 'main' into pr/357
larbish May 13, 2024
c9a73b9
chore(deps): upgrade
larbish May 13, 2024
7a3199c
chore(deps): upgrade
larbish May 17, 2024
f7cbc2f
fix(user): populate only once in plugin
larbish May 17, 2024
4dc3828
refactor(user): simplify auth state change handler
felixgabler May 17, 2024
714dbc4
refactor(user): transform useSupabaseUser back to non-async
felixgabler May 17, 2024
b45770c
refactor: clean up cache handling of server composables
felixgabler May 17, 2024
0895ccc
refactor(session): remove async from useSupabaseSession and manage th…
felixgabler May 17, 2024
9cd48ff
fix: do not modify headers after they have been sent
felixgabler May 18, 2024
92bf923
Merge pull request #1 from felixgabler/fg/plugin_controlled_session
felixgabler May 21, 2024
72e2120
chore: update @supabase/ssr to pre-release version
felixgabler Jun 13, 2024
e2878b4
chore: upgrade @supabase/ssr to 0.4.0
felixgabler Jun 24, 2024
830161c
refactor: fix lint issue
felixgabler Jun 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions docs/content/2.get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Default:

Default: `sb`

Cookie name used for storing access and refresh tokens, added in front of `-access-token` and `-refresh-token` to form the full cookie name e.g. `sb-access-token`
Cookie name used for storing the redirect path when using the `redirect` option, added in front of `-redirect-path` to form the full cookie name e.g. `sb-redirect-path`

### cookieOptions

Expand All @@ -115,20 +115,25 @@ Options for cookies used to share tokens between server and client, refer to [co

### `clientOptions`

Default:
Default:

```ts
clientOptions: { }
```

Supabase client options [available here](https://supabase.com/docs/reference/javascript/initializing#parameters) merged with default values from `@supabase/ssr`:

```ts
clientOptions: {
auth: {
flowType: 'pkce',
detectSessionInUrl: true,
persistSession: true,
autoRefreshToken: true
autoRefreshToken: isBrowser(),
detectSessionInUrl: isBrowser(),
persistSession: true,
},
}
```

Supabase client options [available here](https://supabase.com/docs/reference/javascript/initializing#parameters).

## Versions

Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@
},
"dependencies": {
"@nuxt/kit": "^3.11.2",
"@supabase/supabase-js": "2.43.0",
"@supabase/ssr": "^0.4.0",
"@supabase/supabase-js": "^2.43.5",
"defu": "^6.1.4",
"pathe": "^1.1.2"
},
"devDependencies": {
"@nuxt/eslint": "^0.3.10",
"@nuxt/eslint": "^0.3.12",
"@nuxt/module-builder": "^0.6.0",
"@nuxt/schema": "^3.11.2",
"@release-it/conventional-changelog": "^8.0.1",
"@types/node": "^20.12.8",
"eslint": "^9.1.1",
"@types/node": "^20.12.12",
"eslint": "^9.4.0",
"nuxt": "^3.11.2",
"prettier": "^3.2.5",
"release-it": "^17.2.1",
Expand Down
6,405 changes: 3,054 additions & 3,351 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

35 changes: 6 additions & 29 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface ModuleOptions {
redirectOptions?: RedirectOptions

/**
* Cookie name, used for storing access and refresh tokens, added in front of `-access-token` and `-refresh-token` to form the full cookie name e.g. `sb-access-token`
* Cookie name used for storing the redirect path when using the `redirect` option, added in front of `-redirect-path` to form the full cookie name e.g. `sb-redirect-path`
* @default 'sb'
* @type string
*/
Expand All @@ -73,14 +73,8 @@ export interface ModuleOptions {
cookieOptions?: CookieOptions

/**
* Supabase Client options
* @default {
auth: {
flowType: 'pkce',
detectSessionInUrl: true,
persistSession: true,
},
}
* Supabase client options (overrides default options from `@supabase/ssr`)
* @default { }
* @type object
* @docs https://supabase.com/docs/reference/javascript/initializing#parameters
*/
Expand Down Expand Up @@ -112,14 +106,7 @@ export default defineNuxtModule<ModuleOptions>({
sameSite: 'lax',
secure: true,
} as CookieOptions,
clientOptions: {
auth: {
flowType: 'pkce',
detectSessionInUrl: true,
persistSession: true,
autoRefreshToken: true,
},
} as SupabaseClientOptions<string>,
clientOptions: {} as SupabaseClientOptions<string>,
},
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
Expand Down Expand Up @@ -211,21 +198,11 @@ export default defineNuxtModule<ModuleOptions>({
nuxt.options.build.transpile.push('websocket')
}

// Optimize @supabase/ packages for dev
// TODO: Remove when packages fixed with valid ESM exports
// https://github.com/supabase/gotrue/issues/1013
// Needed to fix https://github.com/supabase/auth-helpers/issues/725
extendViteConfig((config) => {
config.optimizeDeps = config.optimizeDeps || {}
config.optimizeDeps.include = config.optimizeDeps.include || []
config.optimizeDeps.exclude = config.optimizeDeps.exclude || []
config.optimizeDeps.include.push(
'@supabase/functions-js',
'@supabase/gotrue-js',
'@supabase/postgrest-js',
'@supabase/realtime-js',
'@supabase/storage-js',
'@supabase/supabase-js',
)
config.optimizeDeps.include.push('cookie')
})
},
})
26 changes: 5 additions & 21 deletions src/runtime/composables/useSupabaseSession.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
import type { Session } from '@supabase/supabase-js'
import type { Ref } from 'vue'
import { useSupabaseClient } from './useSupabaseClient'
import { useState } from '#imports'

export const useSupabaseSession: () => Ref<Session | null> = () => {
const supabase = useSupabaseClient()

const sessionState = useState<Session | null>('supabase_session', () => null)

// Asyncronous refresh session and ensure user is still logged in
supabase?.auth.getSession().then(({ data: { session } }) => {
if (session) {
if (JSON.stringify(sessionState.value) !== JSON.stringify(session)) {
sessionState.value = session
}
}
else {
sessionState.value = null
}
})

return sessionState
}
/**
* Reactive `Session` state from Supabase. This is initialized in both client and server plugin
* and, on the client, also updated through `onAuthStateChange` events.
*/
export const useSupabaseSession = () => useState<Session | null>('supabase_session', () => null)
13 changes: 6 additions & 7 deletions src/runtime/composables/useSupabaseUser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { User } from '@supabase/supabase-js'
import { useSupabaseSession } from './useSupabaseSession'
import { computed, type ComputedRef } from '#imports'
import { useState } from '#imports'

export const useSupabaseUser: () => ComputedRef<User | null> = () => {
const session = useSupabaseSession()
const userState = computed(() => session.value?.user)
return userState
}
/**
* Reactive `User` state from Supabase. This is initialized in both client and server plugin
* and, on the client, also updated through `onAuthStateChange` events.
*/
export const useSupabaseUser = () => useState<User | null>('supabase_user', () => null)
9 changes: 5 additions & 4 deletions src/runtime/plugins/auth-redirect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useSupabaseUser } from '../composables/useSupabaseUser'
import { useSupabaseSession } from '../composables/useSupabaseSession'
import type { Ref } from '#imports'
import { defineNuxtPlugin, addRouteMiddleware, defineNuxtRouteMiddleware, useCookie, useRuntimeConfig, navigateTo } from '#imports'
import type { RouteLocationNormalized } from '#vue-router'

Expand Down Expand Up @@ -30,10 +31,10 @@ export default defineNuxtPlugin({
})
if (isExcluded) return

const user = useSupabaseUser()
if (!user.value) {
const session = useSupabaseSession()
if (!session.value) {
if (cookieRedirect) {
useCookie(`${cookieName}-redirect-path`, cookieOptions).value = to.fullPath
(useCookie(`${cookieName}-redirect-path`, { ...cookieOptions, readonly: false }) as Ref).value = to.fullPath
}

return navigateTo(login)
Expand Down
67 changes: 24 additions & 43 deletions src/runtime/plugins/supabase.client.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,42 @@
import { createClient } from '@supabase/supabase-js'
import { createBrowserClient } from '@supabase/ssr'
import type { Session } from '@supabase/supabase-js'
import { useSupabaseSession } from '../composables/useSupabaseSession'
import { defineNuxtPlugin, useRuntimeConfig, useCookie } from '#imports'
import { useSupabaseUser } from '../composables/useSupabaseUser'
import { defineNuxtPlugin, useRuntimeConfig } from '#imports'

export default defineNuxtPlugin({
name: 'supabase',
enforce: 'pre',
async setup() {
const currentSession = useSupabaseSession()
const config = useRuntimeConfig().public.supabase
const { url, key, cookieName, cookieOptions, clientOptions } = config
const { url, key, cookieOptions, clientOptions } = useRuntimeConfig().public.supabase

const supabaseClient = createClient(url, key, clientOptions)
const client = createBrowserClient(url, key, {
...clientOptions,
cookieOptions,
isSingleton: true,
})

const accessToken = useCookie(`${cookieName}-access-token`, cookieOptions)
const refreshToken = useCookie(`${cookieName}-refresh-token`, cookieOptions)
const providerToken = useCookie(`${cookieName}-provider-token`, cookieOptions)
const providerRefreshToken = useCookie(`${cookieName}-provider-refresh-token`, cookieOptions)
const currentSession = useSupabaseSession()
const currentUser = useSupabaseUser()

// Handle auth event client side
supabaseClient.auth.onAuthStateChange((event, session) => {
// Update states based on received session
if (session) {
if (JSON.stringify(currentSession) !== JSON.stringify(session)) {
currentSession.value = session
}
}
else {
currentSession.value = null
}
// Initialize user and session states
const {
data: { session },
} = await client.auth.getSession()
currentSession.value = session
currentUser.value = session?.user ?? null

// Use cookies to share session state between server and client
if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
accessToken.value = session?.access_token
refreshToken.value = session?.refresh_token
if (session.provider_token) {
providerToken.value = session.provider_token
}
if (session.provider_refresh_token) {
providerRefreshToken.value = session.provider_refresh_token
}
}
if (event === 'SIGNED_OUT') {
accessToken.value = null
refreshToken.value = null
providerToken.value = null
providerRefreshToken.value = null
// Updates the session and user states through auth events
client.auth.onAuthStateChange((_, session: Session | null) => {
if (JSON.stringify(currentSession.value) !== JSON.stringify(session)) {
currentSession.value = session
currentUser.value = session?.user ?? null
}
})

// Attempt to retrieve existing session from local storage
await supabaseClient.auth.getSession()

return {
provide: {
supabase: {
client: supabaseClient,
},
supabase: { client },
},
}
},
Expand Down
61 changes: 32 additions & 29 deletions src/runtime/plugins/supabase.server.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,46 @@
import { defu } from 'defu'
import { createClient } from '@supabase/supabase-js'
import { useSupabaseSession } from '../composables/useSupabaseSession'
import { defineNuxtPlugin, useRuntimeConfig, useCookie } from '#imports'
import { createServerClient, parseCookieHeader } from '@supabase/ssr'
import { getHeader, setCookie } from 'h3'
import { defineNuxtPlugin, useRequestEvent, useRuntimeConfig, useSupabaseSession, useSupabaseUser } from '#imports'
import type { CookieOptions } from '#app'

export default defineNuxtPlugin({
name: 'supabase',
enforce: 'pre',
async setup() {
const { url, key, cookieName, clientOptions } = useRuntimeConfig().public.supabase
const accessToken = useCookie(`${cookieName}-access-token`).value
const refreshToken = useCookie(`${cookieName}-refresh-token`).value
const { url, key, cookieOptions, clientOptions } = useRuntimeConfig().public.supabase

const options = defu({
auth: {
flowType: clientOptions.auth.flowType,
detectSessionInUrl: false,
persistSession: false,
autoRefreshToken: false,
},
}, clientOptions)
const event = useRequestEvent()!

const supabaseClient = createClient(url, key, options)
const client = createServerClient(url, key, {
...clientOptions,
cookies: {
getAll: () => parseCookieHeader(getHeader(event, 'Cookie') ?? ''),
setAll: (
cookies: {
name: string
value: string
options: CookieOptions
}[],
) => cookies.forEach(({ name, value, options }) => setCookie(event, name, value, options)),
},
cookieOptions,
})

// Set user & session server side
if (accessToken && refreshToken) {
const { data } = await supabaseClient.auth.setSession({
refresh_token: refreshToken,
access_token: accessToken,
})
if (data) {
useSupabaseSession().value = data.session
}
}
// Initialize user and session states
const [
{
data: { session },
},
{
data: { user },
},
] = await Promise.all([client.auth.getSession(), client.auth.getUser()])
useSupabaseSession().value = session
useSupabaseUser().value = user

return {
provide: {
supabase: {
client: supabaseClient,
},
supabase: { client },
},
}
},
Expand Down
Loading