Capacitor don't store cookies

Hey!

I’ve been pulling my hairs out for a couple hours to try to make capacitor.js works with my Node.js back-end but it seems like there is an issue with cookies:

I have a basic index.html in capacitor, and this as a back-end:

(Express + MongoDB + Passport)

const session = require("cookie-session")
const app = express()

app.use(passport.initialize());
app.use(passport.session());
// other code to setup express, passport etc

app.post("/validate-auth-code", async (req, res) => {
    const user = await User.findOne({ email: 'myemail@email.com' })
    if (!user) return res.status(404).send('User not found')
    req.logIn(user, (err) => { })
    console.log(req.user?.email);
    res.send(user?.email)
})

app.post('/test-user', async (req, res) => {
    console.log(req.user);
})

When I try this in a browser, it works fine, the session is saved and /test-user show the user.

However, when I send it from capacitor localhost:3000 to /test-user, it shows “undefined”.
/validate-auth-code show the user email and the user being logged in, so the issue seems to be that capacitor is not saving cookies

Am I missing something? How can I save the cookies in capacitor.js ?

(the HTML is just a blank page with a h1 and a fetch request in a script)

Sorry if this is a stupid question, I just started using capacitor a couple of hours ago, I’m really struggling with this.

1 Like

I personally wouldn’t rely on Cookies in a Capacitor app for authentication. See my post over here - Server set cookies not working on Capacitor iOS - #2 by twestrick

Since that post though, the Capacitor team improved (or released?) the native Cookie plugin which should greatly help those that need to use cookies. Though not mentioned in the documentation, Set-Cookie was added in v4.6.0.

  • cookies: Use Set-Cookie headers to persist cookies (57f8b39)

You might need to use the native HTTP plugin too? I am not sure. Hopefully someone else can chime who has a working app using the Cookie plugin for auth. I am just giving my two cents because I’ve seen so many posts here about Cookie issues where they don’t seem to be reliable for a mobile Capacitor app :slight_smile:

2 Likes

Perfect! Ended up using storage + bearer token and it works perfectly! Way easier than using cookies haha

1 Like

Hey there!

I know its been some time since this was posted, but i want to confirm that i now have a working setup for this and im gonna share my code so others dont have to search for HOURS just like me on how to properly do it.

I used Vue Ionic

First off: @twestrick, your answer was my guiding light tbh! So thanks for that!
I ended up using the native capacitor cookie plugin: Here

And Capacitor Http: Here

And just wrote up a api service to use everywhere!

Here is the code, have fun! :smiley:

import { CapacitorHttp as Http } from '@capacitor/core';

// API key
const BASE_URL: string = import.meta.env.VITE_APP_API_BASE_URL;

interface ApiResponse<T> {
  data: T;
  status: number;
}

interface ApiError {
  statusCode: number;
  message: string;
}

// Centralized Error Handler
const handleError = (error: any): ApiError => {
  return {
    statusCode: error.status || 500,
    message: error.message || 'An unexpected error occurred',
  };
};

export const apiService = {
  async get<T>(url: string, options: object = {}): Promise<T> {
    try {
      const response: ApiResponse<T> = await Http.get({
        url: `${BASE_URL}${url}`,
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': "YOUR API KEY",
        },
        connectTimeout: 5000,
        readTimeout: 5000,
        webFetchExtra: { credentials: 'include' },
        ...options,
      });
      return response.data;
    } catch (error) {
      throw handleError(error);
    }
  },

  async post<T>(url: string, body: object | null = null, options: object = {}): Promise<T> {
    try {
      const response: ApiResponse<T> = await Http.post({
        url: `${BASE_URL}${url}`,
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': "YOUR API KEY",
        },
        data: body,
        connectTimeout: 5000,
        readTimeout: 5000,
        webFetchExtra: { credentials: 'include' },
        ...options,
      });
      return response.data;
    } catch (error) {
      throw handleError(error);
    }
  },

  async put<T>(url: string, body: object, options: object = {}): Promise<T> {
    try {
      const response: ApiResponse<T> = await Http.put({
        url: `${BASE_URL}${url}`,
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': "YOUR API KEY",
        },
        data: body,
        connectTimeout: 5000,
        readTimeout: 5000,
        webFetchExtra: { credentials: 'include' },
        ...options,
      });
      return response.data;
    } catch (error) {
      throw handleError(error);
    }
  },

  async delete<T>(url: string, options: object = {}): Promise<T> {
    try {
      const response: ApiResponse<T> = await Http.delete({
        url: `${BASE_URL}${url}`,
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': "YOUR API KEY",
        },
        connectTimeout: 5000,
        readTimeout: 5000,
        webFetchExtra: { credentials: 'include' },
        ...options,
      });
      return response.data;
    } catch (error) {
      throw handleError(error);
    }
  },
};

PS: Make sure your cookies handle the domain property correctly aswell ;D
→ Oh and also: adjust the timeouts depending on data-load and your own estimate!

1 Like