Skip to content

Supabase Auth bug exchangeCodeForSession returns "both auth code and code verifier should be non-empty" #1026

@luisfelipeluis49

Description

@luisfelipeluis49

Bug report

  • I confirm this is a bug with Supabase, not with my own application.
  • I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

Architecture:

This is Cross-Platform web-based (SEO focused) project, that was built in CSR due to bad performance using SSR w/ Capacitor framework.

Client: Svelte + Vite + Capacitor

Due to use of vanilla Svelte, to handle navigation our choice was "svelte-routing", for building the app on both web and mobile (iOS and Android) we use Capacitor.

Server: Fastify + Supabase

Our server framework of choice was Fastify, as long w/ Supabase, and thus we need to use the Supabase Auth solutions, what prevent us from using tools like Capacitor Generic OAuth2.


Problem

Following the Supabase guide to implement Google OAuth2, when storing the user session, got an AuthApiError: "invalid request: both auth code and code verifier should be non-empty"


Packages:

// package.json
{
    ...,
    "dependencies": {
        "@fastify/compress": "^8.0.1",
        "@fastify/cookie": "^11.0.2",
        "@fastify/cors": "^10.0.2",
        "@fastify/env": "^5.0.2",
        "@fastify/formbody": "^8.0.2",
        "@fastify/multipart": "^9.0.2",
        "@fastify/static": "^8.0.4",
        "@supabase/ssr": "^0.5.2",
        "@supabase/supabase-js": "^2.47.16",
        "dotenv": "^16.4.7",
        "fastify": "^5.2.1",
        ...
    },
    "packageManager": "[email protected]"
}

Code

Front:

Google sign-in button:

// AuthFormFooter.svelte

<script>
    // -- IMPORTS

    ...

    // -- FUNCTIONS

    async function signInWithOAuth(
        provider
        )
    {
        ...

        try
        {
            let redirectionToUrl;

            switch ( platform )
            {
                case 'android':
                    redirectionToUrl = 'com.myapp://auth';
                    break;
                case 'ios':
                    redirectionToUrl = 'com.myapp://auth';
                    break;
                default:
                    redirectionToUrl = 'http://localhost:5173/auth';
            }

            let data = await fetchData(
                '/api/auth/open-auth',
                {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify( { provider, redirectionToUrl } )
                }
                );

            if ( data.error )
            {
                console.error( 'Server sign-in error:', data.error );
            }
            else
            {
                if ( data.url )
                {
                    window.location.href = data.url;
                }
                else
                {
                    console.error( 'Server sign-in error:', data );
                }
            }
        }
        catch ( error )
        {
            console.error( errorText, error );
        }
    }
</script>

<div class="auth-modal-socials">
    <div
        class="auth-modal-socials-item"
        on:click={() => signInWithOAuth( 'google' )}>
        <span class="google-logo-icon size-150"></span>
    </div>
</div>

Auth Callback:

// AuthPage.svelte

<script>
    // -- IMPORTS

    import { onMount } from 'svelte';
    import { fetchData } from '$lib/base';
    import { navigate } from 'svelte-routing';

    // -- FUNCTIONS

    async function authCallback(
        code,
        next
        )
    {
        try
        {
            let response = await fetchData(
                '/api/auth/callback',
                {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify( { code } )
                }
                );

            if ( response.success )
            {
                navigate( `/${ next.slice( 1 ) }`, { replace: true } );
            }
            else
            {
                console.error( 'Authentication failed' );
            }
        }
        catch ( error )
        {
            console.error( 'Error during authentication', error );
        }
    }

    onMount(
        async () =>
        {
            let params = new URLSearchParams( window.location.search );
            let code = params.get( 'code' );
            let next = params.get( 'next' ) || '/';
            if ( code )
            {
                await authCallback( code, next );
            }
            else
            {
                console.error( 'No code found in query params' );
            }
        }
        );
</script>

Server:

Supabase client configuration:

// supabase_service.js

class SupabaseService
{
    // -- CONSTRUCTORS

    constructor(
        )
    {
        this.client = null;
    }

    // -- OPERATIONS

    initalizeDatabaseClient(
        request,
        reply
        )
    {
        this.client = createServerClient(
            process.env.SUPABASE_DATABASE_URL,
            process.env.SUPABASE_DATABASE_KEY,
            {
                cookies:
                {
                    getAll()
                    {
                        return parseCookieHeader( request.headers.cookie ?? '' );
                    },
                    setAll( cookiesToSet )
                    {
                        cookiesToSet.forEach(
                            ( { name, value, options } ) =>
                            {
                                let serializedCookie = serializeCookieHeader( name, value, options );

                                reply.header( 'Set-Cookie', serializedCookie );
                            }
                            );
                    }
                },
                auth:
                {
                    flowType: 'pkce'
                }
            }
            );
    }

    // ~~

    getClient(
        request,
        reply
        )
    {
        return this.client;
    }
}

// -- VARIABLES

export let supabaseService
    = new SupabaseService();

Auth controller:

// authentication_controller.js

...

// -- FUNCTIONS

...

// ~~

async function openAuth(
    request,
    reply
    )
{
    reply.header( 'Access-Control-Allow-Credentials', true );
    reply.header( 'Access-Control-Allow-Origin', request.headers.origin );

    let { redirectionToUrl, provider } = request.body;

    try
    {
        let { data, error } = await supabaseService.getClient().auth.signInWithOAuth(
            {
                provider,
                options: { redirectTo: redirectionToUrl }
            }
            );

        if ( data.url )
        {
            let url = data.url;

            return reply.code( 200 ).send( { url } );
        }
        else
        {
            return reply.code( 400 ).send( { error: 'auth-sign-in-failed' } );
        }
    }
    catch ( error )
    {
        return reply.code( 500 ).send(
            {
                error: 'Server error', details: error
            }
            );
    }
}

// ~~

async function authCallback(
    request,
    reply
    )
{
    reply.header( 'Access-Control-Allow-Credentials', true );
    reply.header( 'Access-Control-Allow-Origin', request.headers.origin );

    let code = request.body.code;
    let route = request.body.route ?? '/';

    try
    {
        if ( code )
        {
            let { data, error } =
                await supabaseService.getClient().auth.exchangeCodeForSession( code );

            if ( error )
            {
                return reply.code( 400 ).send(
                    {
                        success: false,
                        error: error.message
                    }
                    );
            }

            return reply.code( 200 ).send(
                {
                    success: true,
                    route
                }
                );
        }
        else
        {
            return reply.code( 400 ).send(
                {
                    success: false,
                    error: 'No code provided'
                }
                );
        }
    }
    catch ( error )
    {
        return reply.code( 500 ).send(
            {
                success: false,
                error: 'Server error', details: error
            }
            );
    }
}

// ~~

...

// -- EXPORT

export
{
    ...,
    openAuth,
    authCallback,
    ...
}

Updated all packages, enabled flow type pkce, implemented getAll and setAll instead of get, set and remove on cookies options. But all for nothing, got the same error and couldn't get the solution to this error


System information

  • OS: Windows 11
  • Browser: Microsoft Edge
  • Version of Node.js: 20.16.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions