Mobile Development
Supabase Login Not Working in Your Capacitor App? Fix Google Sign-In, Magic Links and Resets
Login works on the web but breaks in the app: Google errors, magic links open Safari, resets land on your website. Here is why, and the step-by-step fix with Capacitor and Supabase.
Kalemba Jamil9 min readYour Lovable or Bolt app logs in perfectly in the browser. Then you wrap it with Capacitor and on the phone things break: Google sign-in shows an error, the magic link opens Safari instead of the app, or the password reset link lands on your website and the app never knows.
All three failures have one cause: the login finishes on a web address the app does not own. Give the app an address of its own and they go away together.
Why login breaks inside the app
Supabase logins that leave your app, like Google sign-in, magic links and password resets, finish by redirecting to a URL. On the web, that URL is your website, so everything just works.
Inside a Capacitor app, the redirect has two problems:
- It goes to the wrong place. The redirect points at
https://yourapp.com, so the user lands in a browser on your website, logged in there but not in the app. - Google blocks sign-in inside embedded web views. If your app opens the Google login page inside its own web view, Google refuses it. The login has to happen in the system browser.
The fix is to give your app its own URL that the phone knows to open in the app, send Supabase's redirect there, and finish the login inside the app.
Step 1: Give the app its own URL scheme
Use your app ID as the scheme, for example com.yourcompany.yourapp. Your callback URL will then be com.yourcompany.yourapp://login-callback/.
iOS: add the scheme to ios/App/App/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.yourcompany.yourapp</string>
</array>
</dict>
</array>
Android: add an intent filter inside the main <activity> in android/app/src/main/AndroidManifest.xml:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.yourcompany.yourapp" />
</intent-filter>
Step 2: Allow the URL in Supabase
In the Supabase dashboard, open Authentication → URL Configuration and add your app's URL to the Redirect URLs, for example com.yourcompany.yourapp://**. Supabase only redirects to URLs on this list (see Supabase redirect URLs), and a missing entry is the most common reason users end up on your website instead of in the app.
Step 3: Use the PKCE flow
With the PKCE flow, Supabase sends back a short-lived code instead of tokens in the URL, and the app exchanges it for a session. Set it where you create the client:
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
auth: { flowType: 'pkce' },
});
Your web version keeps working: with detectSessionInUrl left on (the default), the browser handles the code automatically.
Step 4: Open Google sign-in in the system browser
Install the Browser and App plugins (npm install @capacitor/browser @capacitor/app, then npx cap sync). On the phone, ask Supabase for the login URL without redirecting, and open it in the system browser:
import { Capacitor } from '@capacitor/core';
import { Browser } from '@capacitor/browser';
import { supabase } from './supabase';
const APP_REDIRECT = 'com.yourcompany.yourapp://login-callback/';
export async function signInWithGoogle() {
if (!Capacitor.isNativePlatform()) {
// Website: normal redirect flow
return supabase.auth.signInWithOAuth({ provider: 'google' });
}
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: { redirectTo: APP_REDIRECT, skipBrowserRedirect: true },
});
if (error) throw error;
await Browser.open({ url: data.url });
}
Step 5: Catch the link and finish the login
When Supabase redirects to your app's URL, the phone opens the app and Capacitor fires an appUrlOpen event (see Capacitor deep links). Read the code from the URL and exchange it for a session. Register this once, when the app starts:
import { App } from '@capacitor/app';
import { Browser } from '@capacitor/browser';
import { supabase } from './supabase';
App.addListener('appUrlOpen', async ({ url }) => {
if (!url.startsWith('com.yourcompany.yourapp://')) return;
const code = new URL(url).searchParams.get('code');
if (code) {
const { error } = await supabase.auth.exchangeCodeForSession(code);
if (error) console.error('Login failed', error.message);
}
// Close the browser sheet if it is still open
await Browser.close().catch(() => {});
});
Your existing onAuthStateChange listener then sees the new session, and the rest of the app works as it does on the web.
Magic links and password resets
Email links use the same mechanism. Pass your app URL when you send the email:
// Magic link
await supabase.auth.signInWithOtp({
email,
options: { emailRedirectTo: 'com.yourcompany.yourapp://login-callback/' },
});
// Password reset: use a separate path so the app can show
// the "choose a new password" screen
await supabase.auth.resetPasswordForEmail(email, {
redirectTo: 'com.yourcompany.yourapp://reset-password/',
});
In the appUrlOpen listener, check the path: after exchanging the code from a reset-password link, send the user to your new-password screen and call supabase.auth.updateUser({ password }).
One limitation to know: with PKCE, the link must be opened on the same phone that requested it, because the app holds the secret needed to finish the exchange. Say so in your email text ("open this link on your phone").
Checklist when it still does not work
- Lands on your website: the app URL is missing from Supabase's Redirect URLs, or
redirectTowas not passed. - Link does nothing on the phone: the scheme in Info.plist or AndroidManifest.xml does not exactly match the one in your redirect URL. Rebuild the native app after changing them.
- Google shows an error page: the login is opening inside the app's web view. Use
skipBrowserRedirectand the Browser plugin as above. - Error "both auth code and code verifier should be non-empty": the app received a
codebut cannot find the secret (the code verifier) that was saved when login started. Usually the login was started on another device or in the browser instead of the app, the client was created with different storage than the one that started the login, or app data was cleared in between. Start the login from the app and finish it on the same phone. - App opens but the user is not logged in: the
appUrlOpenlistener is not registered at startup, or the client is not usingflowType: 'pkce', so there is nocodeto exchange. - Sign in with Apple: if you offer Google sign-in on iOS, Apple generally expects an equivalent privacy-focused option such as Sign in with Apple (guideline 4.8).
Custom URL schemes are the quickest route. For links that open the app from normal https:// addresses, set up universal links (iOS) and app links (Android) as described in the Capacitor deep links guide.
If you would rather hand it over: we fix Supabase authentication in mobile apps built from Lovable, Bolt, Base44 and other AI tools: Google and Apple sign-in, magic links, password resets and deep links on both iOS and Android. See our Supabase fixes, read how to publish a Lovable app to the App Store, or message us on WhatsApp for a free check.
Frequently asked questions
Why does Google sign-in fail in my Capacitor app but work on the website?
Google does not allow its sign-in page inside embedded web views. Open the login URL in the system browser with the Capacitor Browser plugin and return to the app through your custom URL scheme.
Do I need universal links for Supabase login?
No. A custom URL scheme such as com.yourcompany.yourapp:// is enough for login callbacks. Universal links and app links are useful when you also want normal web links to open the app.
Why does the magic link work on my phone but not on my laptop?
With the PKCE flow the link has to be completed on the device that requested it. Ask users to open the email on the same phone they are signing in on.
- #Supabase
- #Capacitor
- #Authentication
- #Deep links
- #Lovable