Это кроссплатформенный веб-проект (ориентированный на SEO), созданный на >CSR из-за плохой производительности при использовании SSR с инфраструктурой Capacitor.
Клиент: Svelte + Vite + Capacitor
Поскольку мы использовали стандартный Svelte, для управления навигацией мы выбрали "svelte-routing". Для создания приложения как в Интернете, так и на мобильных устройствах (iOS и Android) мы используем Capacitor.< /p>
Сервер: Fastify + Supabase
Нашей серверной платформой был выбран Fastify с Supabase, поэтому нам необходимо использовать решения Supabase Auth. , что мешает нам от использования таких инструментов, как Capacitor Generic OAuth2.
Проблема
Следование руководству Supabase по реализации Google OAuth2 при хранении в сеансе пользователя получена ошибка AuthApiError: "неверный запрос: код аутентификации и средство проверки кода должны быть непустыми"
Пакеты:
Код: Выделить всё
// 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]"
}
Спереди:
Кнопка входа в Google:
Код: Выделить всё
// AuthFormFooter.svelte
// -- 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 );
}
}
signInWithOAuth( 'google' )}>
Код: Выделить всё
// AuthPage.svelte
// -- 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' );
}
}
);
Конфигурация клиента Supabase:
Код: Выделить всё
// 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
...
// -- 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,
...
}
Подробнее здесь: https://stackoverflow.com/questions/793 ... th-code-an