Supabase auth on server side using Netlify functions?

I am trying to implement several Supabase authentication flows using Netlify edge functions (as to not leak Supabase client keys in the browser.) Some of these flows, including signIn with magic link and resetting password, include sending an email to a user which they open, and it contains some data related to the session in the URL – which is then used with the authentication flow (e.g. verify sign in using magic link, or permission to reset password for user.)

I asked about this in a Supabase help forum, and they said the following:

You normally have to redirect to a browser page to for the url link to be processed.

I don’t do server side stuff and not sure in a netlify function exactly what is going to happen. On a server you have to have persistSession:false in your createClient as there is not local storage. I am also skeptical you will get the session tokens as they are behind a fragment in the redirect URL and it won’t see them on a server.

… You probably need to look into a framework that supports sever side pages and then use auth-helpers which uses cookies to communicate among all the various clients. It handles the redirects with pkce which does work on the server. It is not directly related to edge type functions (I assume a netlify function is that, but not sure). Normally for a separate server type function you have to set the Authorization header and then read the header in the function to get the user jwt. But you already have to be signed in to have that jwt.

Can anybody recommend a way to do configure a netlify deployment, so that when a user clicks a link in their email to sign in via magic link or reset their password, it will work as expected? I am doing this in nodeJS but mainly just HTML, vanilla JS, and htmx for the software stack.

Could you please explain the issue you’re facing? You’ve only mentioned what you’re doing, but you haven’t told what the problem is that you’re seeing.

Also, it would be much more useful if you can share your site so we can check in more detail.

I’m assuming you’re talking about Netlify Functions, not Edge Functions (since you say you’re using Ndoe.js).

1 Like

My problem is that when a user opens a magic link in their email (which directs to a Netlify function getSessionWithOtp) I am not sure how to extract the authCode in the function code to include it in supabase.auth.exchangeCodeForSession(authCode) and authenticate the session/user. Do you know any code that could work for this? My code for the getSessionWithOtp function is below, where the user is directed after opening a magic link. I am currently guessing at what kind of object (e.g. event, headers, url?) could contain the authCode for exchangeCodeForSession.

require('dotenv').config();
const querystring = require('querystring');


const { createClient } = require('@supabase/supabase-js');
const { access } = require('fs');

const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_API_KEY;
const supabase = createClient(supabaseUrl, supabaseKey, {
    auth: {
      flowType: 'pkce',
    },
  });


  exports.handler = async (url, event, headers, request, cookies, req, res, context, body) => {

    console.log('url')
    console.log(url)
    console.log('context')
    console.log(context)
    console.log('body')
    console.log(body)
    console.log('event')
    console.log(event)
    console.log('headers')
    console.log(headers)
    console.log('cookies')
    console.log(cookies)
    console.log(request);
    console.log(req)
  
    // check for code in url querystring
    const code = url.headers['cookie'];
  
    console.log(code);
    const { data, error } = await supabase.auth.exchangeCodeForSession(code)


    if (error) {
        return {
          statusCode: 500,
          body: JSON.stringify(error),
        };
      } else {
        console.log(JSON.stringify(data))
    
        return {
            statusCode: 200,
            body: (JSON.stringify(data))
          };
      }
    }

With this code, after clicking the magic link that gets sent to my email, I get the error:

AuthApiError - unsupported_grant_type

/Users/sam/coop-media/node_modules/@supabase/gotrue-js/dist/main/lib/fetch.js:41:20
processTicksAndRejections (node:internal/process/task_queues:96:5)

Also FYI, the function that takes a user’s email and generates the magic link is here:

require('dotenv').config();
const querystring = require('querystring');


const { createClient } = require('@supabase/supabase-js');

const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_API_KEY;
const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    flowType: 'pkce',
  },
})

exports.handler = async (event) => {
    if (event.httpMethod !== 'POST') {
      return { statusCode: 405, body: 'Method Not Allowed' };
    }

    console.log('test');

    const form = querystring.parse(event.body);
    console.log(form.email);

    const { user, session, error } = await supabase.auth.signInWithOtp({
        email: form.email,
        options: {
            emailRedirectTo: 'http://localhost:8888/getSessionWithOtp',
          },
      })

      if (error) {
        return {
          statusCode: 500,
          body: JSON.stringify(error),
        };
      } else {
        console.log(JSON.stringify(user))

        return {
            statusCode: 200,
            body: (JSON.stringify(user), session)
          };
      }
    }

and my netlify.toml file which is handling the redirects is here:

[build]
  publish = "public"
  functions = "functions"

[[redirects]]
  from = "/feed"
  to = "/feed"
  status = 200

[[redirects]]
  from = "/library"
  to = "/library"
  status = 200

[[redirects]]
  from = "/success"
  to = "/success"
  status = 200


[[redirects]]
  from = "api/*"
  to = "/.netlify/functions/getMessage/:splat"
  status = 200

  [[redirects]]
  from = "/getSessionWithOtp*"
  to = "/.netlify/functions/getSessionWithOtp/:splat"
  status = 200

If you can provide any assistance with this, it would be hugely appreciated!

For future reference, Netlify redirects erase all data after # — and the magic link urls from Supabase contain a #, after which all of the tokens are included.

I solved this by first redirecting the magic link to a client side page, then taking the refresh token from the magic link url in the browser and submitting it as a query parameter in an XHR request to a Netlify function page, and authenticating by getting the refresh token from the query parameter in the Netlify function page and sending it to Supabase with this code (after creating the Supabase client in the Netlify function page):

    const { data, error } = await supabase.auth.refreshSession({ refresh_token })
    const { session, user } = data

I also learned that Netlify redirects in general erase query parameters, and the redirect or Netlify.toml file must be adjusted to include query parameters that should be included on the redirect (documentation here: Redirect options | Netlify Docs)

thanks for coming back and sharing your solution.

1 Like