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>
Button to open google auth (use your backend)
const openOAuthPage = async () => {
await Browser.open({
url: '<http://127.0.0.1:4000/oauth2/authorize>',
});
};
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>
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;
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;