Setting up Supabase Google OAuth with Capacitor (android)

Can somehow help implementing this? I’ve been trying everything but can’t get it to work…

→ I think I have to set flowType to pkce?

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
	auth: {
		flowType: 'pkce',

→ I enabled cookies; do I have anything else to facilitate this?

    "CapacitorCookies": {
      "enabled": true

→ I set up Google Cloud console + Supabase correctly. When running:

  const signInWithProvider = async (provider) => {
    const redirectTo = 'com.myapp.myapp://auth';
    const { error } = await supabase.auth.signInWithOAuth({
      options: { redirectTo },
    if (error) console.error(error);

→ I’m getting back a response containing access_token, code, id_token (with user data); etc.

I’ve tried setSession and However I’m unable to turn this into a session:

const { data, error } = supabase.auth.setSession({ access_token: varA, refresh_token: varB });

I’ve also tried exchangeCodeForSession but I always run into errors.

await supabase.auth.exchangeCodeForSession(code)

It’s unclear to me how I can use the response from Google to start a session; supposedly the session should be created by the supabase client but I can’t make it work.

Could someone please walk me through the correct implementation of this?

I got that all working, the problem is getting the user/session after login, but I guess for that I should find specific Supabase help, not Capacitor…

If you take the time to search the docs, it shows the answer you seek. and yes this has nothing to do with capacitor or ionic.

@Henk007 Did you ever get this working? Any tips? Struggling as well

I simply postponed it but will be trying again tonight, I saw supabase published a flutter example which should be pretty similar.

In any case if I get it working tonight will publish a little how to here or answer questions you might have.

Appreciate the response!

Finally got round to it and got it to work (it’s just a whole lot of moving parts). If you still can use some help I can do a little write up.

Would love a little write up when you have time. Still would be super beneficial to me. Never got it working.


Alright, this is everything I set up:

A. Google Cloud Console

  • Create a OAuth 2.0 Client ID, type Web application
  • As Authorised redirect URIs set your supabase url:
  • Under OAuth consent screen > Edit app > Authorised domains I have the supabase url ( and under scopes I added …/auth/userinfo.profile and openid.

B. Supabase dashboard

  • Enable Google under Providers
  • Enter Client ID & Secret from the Google Cloud Console
  • Under Auth > URL Configuration set your deep link url as Site URL, like so:
  • On the same page I added under Redirect URLs

C. Your app

  • Link your login button to this function:
  const loginWithGoogle = async () => {
    const { data, error } = await supabase.auth.signInWithOAuth({
      provider: "google",
      options: {
        queryParams: {
          access_type: "offline",
          prompt: "consent",

Btw, my Supabase client is just standard:

import { createClient } from '@supabase/supabase-js';

const supabaseUrl = '';
const supabaseAnonKey = 'AnonKey';

const options = {
  auth: {
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: true

export const supabase = createClient(supabaseUrl, supabaseAnonKey, options);

Next, Google will open your app using the set Supabase url, so that’s There are multiple ways to ‘catch’ this, for the moment I have this working, will probably change/improve later but it works:

    App.addListener("appUrlOpen", async (data) => {
      console.log('opened app url');
      console.log('that happened');

      const url = new URL(data.url);
      const hashParams = new URLSearchParams(url.hash.substring(1)); // Remove the '#' at the start of the hash

      const access_token = hashParams.get('access_token');
      const refresh_token = hashParams.get('refresh_token');
      const expires_in = hashParams.get('expires_in');
      const token_type = hashParams.get('token_type');

      if (access_token && refresh_token && expires_in && token_type) {
        // Set the session
        const { data: session, error } = await supabase.auth.setSession({

        if (error) {
          console.error('Error during setting session:', error);
        } else {
          console.log('Session:', session);
          // Fetch and store the user data
          const user = session.user;
          await SecureStoragePlugin.set({ key: 'user', value: JSON.stringify(user) });
  }, []);

D. Android Deep Link

Last thing is that we have to set up our app to open our link ( In AndroidManifest.xml I have:

            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="" />

Now, I added similar things to config.capacitor.json, however I’m not sure which parts are necessary and I’m too lazy to figure out:

  "appId": "",
  "appName": "appName",
  "webDir": "build",
  "scheme": "",
    "deepLinks": {
    "scheme": ""

I also have CapacitorCookies and GoogleAuth enabled under plugins, again I’m not sure if this is necessary:

  "plugins": {
    "GoogleAuth": {
      "scopes": ["profile", "email"],
      "serverClientId": "yourClientID",
      "forceCodeForRefreshToken" : true
    "CapacitorCookies": {
      "enabled": true

So… I believe that’s everything.

:clap: I’ll give it a shot in the next few days. Thanks so much!

It worked! You don’t even know how many hours I sunk into this. Think the key for me was mainly using the App.addListener as I believe I had most of the other intricacies in place.

Regardless thank you very much for all the detail.

The weird thing for me was when using something slightly different in Androidmanifest, the url that was returned was a different one with a pcke code in it… causing a lot of confusion.