You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Supabase Auth调用exchangeCodeForSession时提示"invalid request: both auth code and code verifier should be non-empty"问题求助

Supabase Auth调用exchangeCodeForSession时提示"invalid request: both auth code and code verifier should be non-empty"问题求助

大家好,我现在在做一个跨平台项目,遇到了Supabase OAuth2登录的问题,折腾了好久都没解决,来求助各位!

先简单说下我的项目架构:

这是一个跨平台Web项目(侧重SEO),因为用SSR搭配Capacitor框架时性能太差,所以最终采用了CSR模式开发。

客户端:Svelte + Vite + Capacitor

因为用的是原生Svelte,所以导航用的是*"svelte-routing"*;跨Web、iOS和Android端打包用的是Capacitor。

服务端:Fastify + Supabase

服务端框架选的是Fastify,搭配Supabase使用。因为要用到Supabase的Auth方案,所以没法用Capacitor的Generic OAuth2这类工具。


问题详情

我按照Supabase的Google OAuth2集成指南来实现第三方登录,但是在调用exchangeCodeForSession存储用户会话的时候,一直报AuthApiError:"invalid request: both auth code and code verifier should be non-empty"。


使用的依赖包
// 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": "yarn@4.5.1"
}

相关代码片段

前端代码

Google登录按钮组件(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>

授权回调页面(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>

服务端代码

Supabase客户端配置(supabase_service.js)

// 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();

授权控制器(authentication_controller.js)

// 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,
    ...
}

我已经尝试了以下操作,但都没解决问题:

  • 更新了所有依赖包到最新版本
  • 在Supabase客户端配置中开启了flowType: 'pkce'
  • 把cookies选项从原来的get/set/remove改成了getAll/setAll

但还是一直报同样的错误,有没有大佬知道这是哪里出问题了?麻烦帮忙看看,谢谢了!

备注:内容来源于stack exchange,提问作者luisfelipeluis49

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 03:23:16