1. Implement the Google Auth on payload guide and modify the callback function on the backend

follow the guide about how to implement Google Auth on payloadcsm. We use a custom redirect the app can listen to and receive the token.

// replace the redirect with this redirect
res.redirect(`myapp://oauth-callback?token=${encodeURIComponent(token)}`);

register the listener in the info.plist for ios (you may also do it in xcode)

<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>myapp</string>
			</array>
		</dict>
	</array>

2. Initiate Login

Button to open google auth (use your backend)

const openOAuthPage = async () => {
    await Browser.open({
      url: '<http://127.0.0.1:4000/oauth2/authorize>',
    });
  };

3. Handle the login response

For this we need to add a listener to the app that waits for the oauth-callback url

Wrap your app with an AppUrlListener

// This component listens for URL changes to handle OAuth token extraction and app authentication.
function AppUrlListener() {
  // Use the 'tokenAtom' state to manage authentication status.
  const [isAuth, setIsAuth] = useAtom(tokenAtom);

  // Effect hook to add a listener for the 'appUrlOpen' event from the App.
  useEffect(() => {
    // Add the listener for 'appUrlOpen' events.
    App.addListener('appUrlOpen', async data => {
      // Check if the opened URL is the OAuth callback, identified by the "myapp://" scheme.
      if (!data.url.startsWith("myapp://")){
        return; // If the URL does not match, exit the function.
      }

      // Extract the token from the URL.
      const token = extractTokenFromUrl(data.url);
      // Save the extracted token for later use.
      await setToken(token);
      // Close the browser window if opened via OAuth flow.
      await Browser.close();
      // Update authentication status and navigate to a specific route if needed.
      setIsAuth(true);
    });
  }, []); // The empty dependency array ensures this effect runs only once at mount.

  // Since this component does not render anything, it returns null.
  return null;
}

// Utility function to extract the 'token' query parameter from a given URL.
function extractTokenFromUrl(url) {
  // Parse the URL string into a URL object.
  const urlObj = new URL(url);
  // Extract the 'token' parameter from the query string.
  const token = urlObj.searchParams.get('token');
  return token;
}

// Asynchronous function to save the extracted token into the app's preferences.
async function setToken(token) {
  await Preferences.set({
    key: 'token', // The key under which the token is stored.
    value: token, // The actual token to be stored.
  });
}

Example:

<IonApp>
      <IonReactRouter>
        <AppUrlListener />
        <IonRouterOutlet id="main" animation={pageTransition}>
          <Route
            path="/tabs"
            render={() => (isAuthenticated ? <Tabs />: <Redirect to="/login" />)}
          />
          <Route
            path="/login"
            render={() => (isAuthenticated ? <Redirect to={'/tabs'} /> : <LoginScreen />)}
          />
          <Route
            path="/"
            render={() => <Redirect to={isAuthenticated ? '/tabs' : '/login'} />}
            exact={true}
          />
        </IonRouterOutlet>
      </IonReactRouter>
    </IonApp>

4. Making calls to your backend

define a custom api with axios to always include the token when request are send to the server

import axios from 'axios';
import { Preferences } from '@capacitor/preferences';

// Create an Axios instance
const api = axios.create({
  baseURL: '<http://127.0.0.1:4000/api>', // Replace with your API's base URL
  withCredentials: true
});

// Set up a request interceptor
api.interceptors.request.use(async (config) => {
  // Retrieve the token from storage
  const { value: token } = await Preferences.get({ key: 'token' });

  // If the token exists, set it in the header
  if (token) {
    config.headers.Authorization = `JWT ${token}`;
  }

  return config;
}, (error) => {
  // Do something with request error
  return Promise.reject(error);
});

export default api;

5. Call the server

now to get user information from the server simply call the api and display it

import api from '../utils/api';
import { useState, useEffect } from 'react';

const Settings = () => {
  // State to hold user information
  const [userInfoOBJ, setUserInfoOBJ] = useState(null);

  // Effect to fetch user information on component mount
  useEffect(() => {
    const fetchUserInfo = async () => {
      try {
        // Attempt to get user data from the API
        const response = await api.get('/users/me');
        // If successful, update the state with the fetched data
        setUserInfoOBJ(response.data);
      } catch (e) {
        // Log any errors to the console
        console.log(e);
      }
    };

    // Call the fetch function
    fetchUserInfo();
  }, []); // Empty dependency array means this effect runs once on mount

  return (
    <div className="flex flex-row gap-2 items-center">
      {/* Conditional rendering based on user pictureURL */}
      {userInfoOBJ?.user?.pictureURL ? (
        <div className="w-12 h-12 rounded-full overflow-hidden">
          <img src={userInfoOBJ?.user?.pictureURL} alt="profile" className="h-12 w-12" />
        </div>
      ) : (
        ''
      )}
      {/* Display user email or loading text */}
      <div className="font-bold">
        {userInfoOBJ?.user?.email ? userInfoOBJ?.user?.email : "Loading..."}
      </div>
    </div>
  );
};

export default Settings;