supabase / auth-helpers

A collection of framework specific Auth utilities for working with Supabase.

Home Page:https://supabase.github.io/auth-helpers/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Create auth helpers for Remix

VictorPeralta opened this issue · comments

Like with Next.js which currently has easy to use helpers and routes, the same could be done for Remix.

I didn't find an open issue about this, although I heard it's planned, so it can be tracked here.

If no one is working on this yet, I'd like work on it.

I have something like the next + supabase auth example for Remix here, so I feel like ai have enough context to accomplish this:

https://github.com/VictorPeralta/supabase-remix-auth

Hi Victor,
PRs are most definitely welcome! Thanks for opening this issue to track.

One consideration we have with Remix and Supabase Auth is ensuring realtime still works on the client side. Remix mostly uses HttpOnly cookies for auth but these currently don't play well with the client side realtime. This is something we're looking to address in the future, but if you can get it working with the current constraints, that would be great!

@VictorPeralta that would be awesome indeed. I know that @dijonmusters was planning to work on this once he's back from leave, but I am very certain he wouldn't mind you getting started on it 💚

commented

Looks like @dijonmusters had already started working on this: 2a690e5

Might a good starting point Victor! Let me know if you need help, would be happy to contribute

Also worthwhile looking at https://github.com/rphlmr/supa-fly-stack and if there are things from there we can pull out into auth helpers cc @rphlmr

@dijonmusters Hi, i tried the example from the branch feat/add-remix-run but i have trouble to use the withAuthRequired function. When i use it, i get the following application error:

Error: Cannot initialize 'routeModules'. This normally occurs when you have server code in your client modules.

the problem is this line:

if (!user) return redirect('/login')

redirect comes from the @remix-run/node package so its not available in browser context. When i try to change withAuthRequired.js to withAuthRequired.server.js nothing changed. I get the same application error.

I also read something about the error in the remix docs: Server Code in Client Bundles but i didn't find any solution.

Does anyone have an idea how to solve this? I would like to use this wrapper because it makes things easier, but it doesn't seem to be possible...

@niklasgrewe Hi,

It's because withAuthRequired is a high order function that creates a side effect.

Remix doesn't know what to do with that and add this code to client side bundle too.

https://remix.run/docs/en/v1/guides/constraints#higher-order-functions

You should rewrite it to use it in loader / action 😉

// withAuthRequired.server.js
export default async function withAuthRequired (request) => {
  const accessToken = await getAccessTokenFromRequest(request);
  const { user } = await supabase.auth.api.getUser(accessToken);

  if (!user) {
    throw redirect("/login");
  }

  return { accessToken, user };
};
// app/routes/channels.jsx
export const loader = async ({ request }) => {
  const { accessToken } = await withAuthRequired(request)
  supabase.auth.setAuth(accessToken);

  const { data: channels } = await supabase.from("channels").select("*");

  return json({
    channels,
  });
};

PS : return json() will be mandatory soon ;)

@rphlmr thank you for your quick help. Do you know when the auth helper for remix will be ready? I had already considered using remix-auth with the supabase strategy. However, I had seen that there is also a PR from you pending. Can you tell me where the differences are? Honestly don't know what is the best/easiest way to integrate supabase auth into remix. Can you recommend me something?

@rphlmr thank you for your quick help. Do you know when the auth helper for remix will be ready? I had already considered using remix-auth with the supabase strategy. However, I had seen that there is also a PR from you pending. Can you tell me where the differences are? Honestly don't know what is the best/easiest way to integrate supabase auth into remix. Can you recommend me something?

The remix-auth-supabase is on standby 😅
With the maintainer we think to deprecate it, because we think Remix Stacks are more suitable.

I can recommend you to check "my remix stack" .
At least to have an overall idea about how it could be implemented, until supabase made an official way ;)

@rphlmr great thank you, I did not know that. I also think the remix stacks are more suitable. Your stack I have already looked at, but admittedly, I do not really understand everything yet 😅

What I would like to understand though is why do I need to return json({ accessToken, User }) instead of return { accessToken, User } Can you possibly explain this to me? Most importantly, I would like to use types. But when I return json, I can only specify Promise<Response> as the return type. Do you know how I can specify the types for accessToken and User anyway?

I would like to understand though is why do I need to return json({ accessToken, User }) instead of return { accessToken, User }

@niklasgrewe
It's a Remix thing. At first, they had no opinion but to prevent future issues they advise to return json object with json.

It's an helper function equivalent to

new Response({ accessToken, User }, {
    headers: {
      "Content-Type": "application/json",
    },
  });

But when I return json, I can only specify Promise<Response> as the return type. Do you know how I can specify the types for accessToken and User anyway?

Typing a loader or action is not possible because they are just Fetch Response.
Response body (your { accessToken, user }) is just a readable stream of byte data in the end ;)
They are serialized thru the network and not directly used in your code like "normal" functions

But you can (and should) anyway use typing for useLoaderData and useActionData.

Example :

interface MyLoaderData {
  orders: {
    id: string;
    amount: number;
    ...
  };
}

export const loader = async ({ request }) => {
  const { accessToken } = await withAuthRequired(request);
  supabase.auth.setAuth(accessToken);

  const { data: orders } = await supabase.from("orders").select("*");

  return json({
    orders,
  });
};

export default function MyRoute () {
  const { orders } = useLoaderData() as MyLoaderData
  // or const { orders } = useLoaderData<MyLoaderData>()
  // its a matter of choice, Remix creator like using as ^^"

  return <div>...</div>
}

I hope it helps.
Don't worry, take your time, and play with it.
It's a new tech with lot of "old things" (HTTP standards re-implementation) coming back.

@rphlmr thank you very much for the detailed info. However, could it be that return json({ accessToken, user }); is not currently supported? When i try this:

// withAuthRequired.server.js
export default async function withAuthRequired (request) => {
  const accessToken = await getAccessTokenFromRequest(request);
  const { user } = await supabase.auth.api.getUser(accessToken);

  if (!user) {
    throw redirect("/login");
  }

  return json({ accessToken, user }); // ❌ not working
  // return { accessToken, user } ✅ works
};
// app/routes/private.jsx
export const loader = async ({ request }) => {
  const { user } = await withAuthRequired(request)
  return json({ email: user.email })
};

i always get

Cannot read properties of undefined (reading 'email')

When i change return json({ accessToken, user }); to return { accessToken, user } it works

@niklasgrewe I'm so sorry, I've edited my previous example => #57 (comment)

Of course, withAuthRequired have to return "classic json" and not json({}) that is a "http layer thing".
Sorry again for the confusion :(

Sorry again for the confusion :(

all good, no problem at all, I figured it out myself. Thanks again for your support and explanations

Any news on Remix support?

Supabase 🤝 Remix?

image

Chiming in to remind people that Cloudflare Pages/Workers is a supported deployment environment for Remix that doesn't support environment variables. Seems like all the stacks mainly use Fly. Hoping these helpers will keep that in mind and make sure it's compatible with Cloudflare.

Has anyone used an oath supabase login with the remix auth helpers? I'm only seeing an email/password login example in the root https://github.com/supabase/auth-helpers/blob/4a709e24445d17b5bafae00ea06f562f5e052fcc/examples/remix/app/root.tsx

@lsbyerley I've added an OAuth example here: https://github.com/supabase/auth-helpers/pull/354/files

Thinking about changing the example to use the Auth UI. Would that be helpful?

@lsbyerley I've added an OAuth example here: https://github.com/supabase/auth-helpers/pull/354/files

Thinking about changing the example to use the Auth UI. Would that be helpful?

Awesome, thank you for the updated oauth example! I personally don't use the Auth UI components but I'm sure someone else out there would find it useful. I think having both would be perfect.

I'd second keeping an example without Auth UI. I don't tend to use it either, so having an example without is useful for me too.

The new remix oauth example doesn't seem to be working for me. I see the access token in the url after auth and then the remix app does nothing with it.

@lsbyerley calling the auth methods in a server action wasn't the right approach unfortunately. I'm working on moving auth stuff to the client here: #356 . It currently uses the Auth UI but I'll also add a plain JS example.

This approach will simplify things and save you money (server function invocations).

Thank you! I'm still not sure I understand how to perform a signInWithOAuth authentication without the Auth UI as it seems you've switched back to signInWithPassword.

The earlier example you had for oauth signin did not do anything with the accessToken after coming back to the remix

@lsbyerley by following the createBrowserClient and onAuthStatChange handler setup in the root route (https://github.com/supabase/auth-helpers/blob/main/examples/remix/app/root.tsx#L80-L96) you can now use the standard javascript methods: https://supabase.com/docs/reference/javascript/auth-signinwithoauth

<button
  onClick={() => {
    supabase?.auth.signInWithOAuth({
      provider: 'github',
      options: { redirectTo: 'http://localhost:3004' }
    });
  }}
>
  GitHub OAuth
</button>

I've added it to the example: #360