@@ -32,66 +32,65 @@ It Supports the following:
3232To allow for more freedom and support some of the different authentication types the verify no longer just sends the form,
3333but it now sends the entire request. See [ Setup authenticator & strategy] ( #setup-authenticator-&-strategy )
3434
35- ### Setup sessionStorage
35+ ### Setup sessionStorage, strategy & authenticator
3636``` js
37- // app/session .server.ts
37+ // app/auth .server.ts
3838import { createCookieSessionStorage } from ' remix'
39+ import { Authenticator , AuthorizationError } from ' remix-auth'
40+ import { SupabaseStrategy } from ' @afaik/remix-auth-supabase-strategy'
41+ import { supabaseClient } from ' ~/supabase'
42+ import type { Session } from ' ~/supabase'
3943
4044export const sessionStorage = createCookieSessionStorage ({
41- cookie: {
42- name: ' sb' ,
43- sameSite: ' lax' ,
44- path: ' /' ,
45- httpOnly: true ,
46- secrets: [SESSION_SECRET ],
47- // 1 hour resembles access_token expiration
48- // set it to 1 hour if you want to user to re-log every hour
49- // 24 hours refreshes_token if the user visits every 24 hours
50- maxAge: 60 * 60 * 24 ,
51- secure: process .env .NODE_ENV === ' production' ,
52- },
45+ cookie: {
46+ name: ' sb' ,
47+ httpOnly: true ,
48+ path: ' /' ,
49+ sameSite: ' lax' ,
50+ secrets: [' s3cr3t' ], // This should be an env variable
51+ secure: process .env .NODE_ENV === ' production' ,
52+ },
5353})
54- ```
55-
56- ### Setup authenticator & strategy
57- ``` js
58- // app/auth.server.ts
59- import type { Session } from ' @supabase/supabase-js'
60- import { Authenticator } from ' remix-auth'
61- import { SupabaseStrategy } from ' remix-auth-supabase'
62- import { supabase } from ' ~/utils/supabase'
63- import { sessionStorage } from ' ~/session.server'
6454
6555export const supabaseStrategy = new SupabaseStrategy (
66- {
67- supabaseClient: supabase,
68- sessionStorage,
69- sessionKey: ' sb:session' , // if not set, default is sb:session
70- sessionErrorKey: ' sb:error' , // if not set, default is sb:error
71- },
72- async ({ req, supabaseClient }) => {
56+ {
57+ supabaseClient,
58+ sessionStorage,
59+ sessionKey: ' sb:session' , // if not set, default is sb:session
60+ sessionErrorKey: ' sb:error' , // if not set, default is sb:error
61+ },
7362 // simple verify example for email/password auth
74- const form = await req .formData ()
75- const email = form? .get (' email' )
76- const password = form? .get (' password' )
77- if (! email || typeof email !== ' string' || ! password || typeof password !== ' string' )
78- throw new Error (' Need a valid email and/or password' )
79-
80- return supabaseClient .auth .api .signInWithEmail (email, password).then ((res ) => {
81- if (res? .error || ! res .data )
82- throw new Error (res? .error ? .message ?? ' No user found' )
83-
84- return res? .data
85- })
86- },
63+ async ({ req, supabaseClient }) => {
64+ const form = await req .formData ()
65+ const email = form? .get (' email' )
66+ const password = form? .get (' password' )
67+
68+ if (! email) throw new AuthorizationError (' Email is required' )
69+ if (typeof email !== ' string' )
70+ throw new AuthorizationError (' Email must be a string' )
71+
72+ if (! password) throw new AuthorizationError (' Password is required' )
73+ if (typeof password !== ' string' )
74+ throw new AuthorizationError (' Password must be a string' )
75+
76+ return supabaseClient .auth .api
77+ .signInWithEmail (email, password)
78+ .then (({ data, error }): Session => {
79+ if (error || ! data) {
80+ throw new AuthorizationError (
81+ error? .message ?? ' No user session found' ,
82+ )
83+ }
84+
85+ return data
86+ })
87+ },
8788)
8889
89- export const authenticator = new Authenticator < Session> (
90- sessionStorage,
91- {
90+ export const authenticator = new Authenticator < Session> (sessionStorage, {
9291 sessionKey: supabaseStrategy .sessionKey , // keep in sync
9392 sessionErrorKey: supabaseStrategy .sessionErrorKey , // keep in sync
94- })
93+ })
9594
9695authenticator .use (supabaseStrategy)
9796` ` `
@@ -101,14 +100,14 @@ authenticator.use(supabaseStrategy)
101100
102101` ` ` js
103102// app/routes/login.ts
104- export const loader: LoaderFunction = async ({ request }) =>
105- supabaseStrategy .checkSession (request, {
106- successRedirect: ' /profile '
107- })
103+ export const loader: LoaderFunction = async ({ request }) =>
104+ supabaseStrategy .checkSession (request, {
105+ successRedirect: ' /private '
106+ })
108107
109108export const action: ActionFunction = async ({ request }) =>
110109 authenticator .authenticate (' sb' , request, {
111- successRedirect: ' /profile ' ,
110+ successRedirect: ' /private ' ,
112111 failureRedirect: ' /login' ,
113112 })
114113
@@ -124,7 +123,7 @@ export default function LoginPage() {
124123` ` `
125124
126125` ` ` js
127- // app/routes/profile .ts
126+ // app/routes/private .ts
128127export const loader: LoaderFunction = async ({ request }) => {
129128 // If token refresh and successRedirect not set, reload the current route
130129 const session = await supabaseStrategy .checkSession (request);
@@ -151,9 +150,9 @@ await supabaseStrategy.checkSession(request, {
151150
152151##### Redirect if authenticated
153152` ` ` js
154- // If the user is authenticated, redirect to /profile
153+ // If the user is authenticated, redirect to /private
155154await supabaseStrategy .checkSession (request, {
156- successRedirect: " /profile " ,
155+ successRedirect: " /private " ,
157156});
158157` ` `
159158
@@ -175,68 +174,80 @@ if (session) {
175174` ` ` js
176175// app/routes/login.ts
177176export const loader: LoaderFunction = async ({ request }) => {
178- // Beware, never set failureRedirect equals to the current route
179- const session = supabaseStrategy .checkSession (request, {
180- successRedirect: ' /profile ' ,
181- failureRedirect: " /login" , // ❌ DONT'T : infinite loop
182- });
183-
184- // In this example, session is always null otherwise it would have been redirected
177+ // Beware, never set failureRedirect equals to the current route
178+ const session = supabaseStrategy .checkSession (request, {
179+ successRedirect: ' /private ' ,
180+ failureRedirect: " /login" , // ❌ DONT'T : infinite loop
181+ });
182+
183+ // In this example, session is always null otherwise it would have been redirected
185184}
186185` ` `
187186
188187#### Redirect to
188+ [Example]("https://github.com/mitchelvanbever/remix-auth-supabase-strategy/tree/main/examples/with-redirect-to")
189189> With Remix.run it's easy to add super UX
190+
190191` ` ` js
191- // app/routes/profile.ts
192+ // app/routes/private. profile.ts
192193export const loader: LoaderFunction = async ({ request }) =>
193- // If checkSession fails, redirect to login and go back here when authenticated
194- supabaseStrategy .checkSession (request, {
195- failureRedirect: ` /login?redirectTo=/profile`
196- });
194+ // If checkSession fails, redirect to login and go back here when authenticated
195+ supabaseStrategy .checkSession (request, {
196+ failureRedirect: ` /login?redirectTo=/private/profile`
197+ });
198+ ` ` `
199+ ` ` ` js
200+ // app/routes/private.ts
201+ export const loader: LoaderFunction = async ({ request }) =>
202+ // If checkSession fails, redirect to login and go back here when authenticated
203+ supabaseStrategy .checkSession (request, {
204+ failureRedirect: ` /login`
205+ });
197206` ` `
198207` ` ` js
199208// app/routes/login.ts
200209export const loader = async ({ request }) => {
201- const redirectTo = new URL (request .url ).searchParams .get (" redirectTo" ) ?? " /dashboard " ;
210+ const redirectTo = new URL (request .url ).searchParams .get (" redirectTo" ) ?? " /profile " ;
202211
203212 return supabaseStrategy .checkSession (request, {
204213 successRedirect: redirectTo,
205214 });
206215};
207216
208217export const action: ActionFunction = async ({ request }) => {
209- // Always clone request when access formData() in action/loader with authenticator
210- // 💡 request.formData() can't be called twice
211- const data = await request .clone ().formData ();
212- // If authenticate success, redirectTo what found in searchParams
213- // Or where you want
214- const redirectTo = data .get (" redirectTo" ) ?? " /dashboard " ;
215-
216- return authenticator .authenticate (' sb' , request, {
217- successRedirect: redirectTo,
218- failureRedirect: ' /login' ,
219- })
218+ // Always clone request when access formData() in action/loader with authenticator
219+ // 💡 request.formData() can't be called twice
220+ const data = await request .clone ().formData ();
221+ // If authenticate success, redirectTo what found in searchParams
222+ // Or where you want
223+ const redirectTo = data .get (" redirectTo" ) ?? " /profile " ;
224+
225+ return authenticator .authenticate (' sb' , request, {
226+ successRedirect: redirectTo,
227+ failureRedirect: ' /login' ,
228+ })
220229}
221230
222231export default function LoginPage () {
223- const [searchParams ] = useSearchParams ();
224-
225- return (
226- < Form method= " post" >
227- < input
228- name= " redirectTo"
229- value= {searchParams .get (" redirectTo" ) ?? undefined }
230- hidden
231- readOnly
232- / >
233- < input type= " email" name= " email" required / >
234- < input
235- type= " password"
236- name= " password"
237- / >
238- < button> Sign In< / button>
239- < / Form>
240- );
232+ const [searchParams ] = useSearchParams ();
233+
234+ return (
235+ < Form method= " post" >
236+ < input
237+ name= " redirectTo"
238+ value= {searchParams .get (" redirectTo" ) ?? undefined }
239+ hidden
240+ readOnly
241+ / >
242+ < input type= " email" name= " email" / >
243+ < input type= " password" name= " password" / >
244+ < button> Sign In< / button>
245+ < / Form>
246+ );
241247}
242248` ` `
249+
250+ ## 📖 Examples
251+ - [Email / password]("https://github.com/mitchelvanbever/remix-auth-supabase-strategy/tree/main/examples/email-password")
252+ - [With redirect to]("https://github.com/mitchelvanbever/remix-auth-supabase-strategy/tree/main/examples/with-redirect-to")
253+
0 commit comments