Auth Connect React

In this tutorial, you will use Ionic React to create a simple tab-based application with a login screen and secure area, using the Auth Connect React bindings with Auth0 as the auth backend.

Installation#

Let's start by installing the Ionic CLI and creating a react starter app:

npm install -g @ionic/cli
ionic start --type=react

Select the "tabs" starter template:

Ionic CLI Template Selection

If you have not already setup Ionic Enterprise in your app, follow the one-time setup steps.

Once Ionic Enterprise is set up, its time to install Auth Connect and the React bindings:

npm install @ionic-enterprise/auth @ionic-enterprise/auth-react
npx cap sync

Setting up Auth0#

Follow this guide to set up your free Auth0 account

Setting up Pages#

In order to secure our sample app, we'll need to create three extra pages:

  • Login Page
  • Logout Page
  • Callback Page

Login Page#

import React, { useEffect } from "react";
import {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
IonButton,
useIonRouter,
} from "@ionic/react";
import { useAuthConnect } from "@ionic-enterprise/auth-react";
interface LoginProps {}
const Login: React.FC<LoginProps> = () => {
const { error, isAuthenticated, login } = useAuthConnect();
const router = useIonRouter();
useEffect(() => {
if (isAuthenticated) {
router.push("/tabs/tab1", "none", "replace");
}
}, [isAuthenticated, router]);
const handleLogin = () => {
login();
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Login</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonButton expand="block" onClick={handleLogin}>
Login
</IonButton>
{error && (
<>
<div>error</div>
<div>{JSON.stringify(error)}</div>
</>
)}
</IonContent>
</IonPage>
);
};
export default Login;

Let's take a deeper look at some of the properties available to us from the useAuthConnect hook.

On the Login page we are destructuring three properties from the hook, error, isAuthenticated and login:

  • error: contains any error generated during the authentication flow
  • isAuthenticated: a boolean representing if the user is authenticated or not
  • login: a function that you call when a user is ready to begin the authentication process (API docs)

These values are stateful, and will automatically update to the current values when they are changed within Auth Connect. When these values are updated, it will trigger a render in the components that consume them.

Logout Page#

import { useIonRouter } from '@ionic/react';
import React, { useEffect } from 'react';
import { useAuthConnect } from '@ionic-enterprise/auth-react';
interface LogoutProps {}
const Logout: React.FC<LogoutProps> = () => {
const router = useIonRouter();
const { handleLogoutCallback } = useAuthConnect();
useEffect(() => {
(async () => {
// use when implicitLogin is set to CURRENT
// await handleLogoutCallback();
// router.push('/tabs/tab1');
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};
export default Logout;

Here, we are using another function from the useAuthConnect hook, handleLogoutCallback. This hook needs to be called during logout callbacks (API docs).

Callback Page#

import { useAuthConnect } from '@ionic-enterprise/auth-react';
import { useIonRouter } from '@ionic/react';
import React, { useEffect } from 'react';
interface CallbackProps {}
const Callback: React.FC<CallbackProps> = () => {
const { handleLoginCallback } = useAuthConnect();
const router = useIonRouter();
useEffect(() => {
async function doCallback() {
try {
// use when implicitLogin is set to CURRENT
// await handleLoginCallback();
// router.push('/tabs/tab1');
} catch (e) { console.error(e)}
}
doCallback();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};
export default Callback;

Another function from useAuthConnect hook used here is handleLoginCallback. This hook needs to be called during login callbacks (API docs).

We will also need to create a few components:

  • UserInfo - for displaying user information after login
  • TabController - for displaying the tab-based interface within a private route

UserCard.tsx#

import React from 'react';
import {
IonCard,
IonCardHeader,
IonCardTitle,
IonCardContent,
IonLabel,
IonItem,
IonAvatar,
} from '@ionic/react';
import { useAuthConnect } from '@ionic-enterprise/auth-react';
interface User {
name: string;
picture: string;
email: string;
}
interface UserCardProps {}
const UserCard: React.FC<UserCardProps> = () => {
const {
accessToken,
refreshToken,
isAuthenticated,
idToken,
} = useAuthConnect<User>();
return (
<IonCard className="provider">
<IonCardHeader>
<IonCardTitle>User Info</IonCardTitle>
</IonCardHeader>
<IonCardContent>
<IonItem lines="none">
<IonAvatar slot="start">
<img src={idToken?.picture} alt={idToken?.name} />
</IonAvatar>
<IonLabel>
<h2>{idToken?.name}</h2>
<p>{idToken?.email}</p>
<p>Status: {isAuthenticated ? 'Logged In' : 'Logged Out'}</p>
<p>Access Token: {accessToken}</p>
<p>Refresh Token: {refreshToken}</p>
</IonLabel>
</IonItem>
</IonCardContent>
</IonCard>
);
};
export default UserCard;

Here we are making use of some user-oriented properties from the hook:

  • accessToken: the access token for use in authenticated API calls
  • refreshToken: used to retrieve a new access token
  • idToken: idToken provides access to data stored the authentication object received from the authentication service provider (the object shape can be defined by providing a type to the useAuthConnect hook)

Remove the original IonTabs container from the main App component and put it into its own component:

TabController.tsx#

const TabController: React.FC = () => (
<IonTabs>
<IonRouterOutlet>
<Route exact path="/tab1">
<Tab1 />
</Route>
<Route exact path="/tab2">
<Tab2 />
</Route>
<Route path="/tab3">
<Tab3 />
</Route>
<Route exact path="/">
<Redirect to="/tab1" />
</Route>
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="tab1" href="/tab1">
<IonIcon icon={triangle} />
<IonLabel>Tab 1</IonLabel>
</IonTabButton>
<IonTabButton tab="tab2" href="/tab2">
<IonIcon icon={ellipse} />
<IonLabel>Tab 2</IonLabel>
</IonTabButton>
<IonTabButton tab="tab3" href="/tab3">
<IonIcon icon={square} />
<IonLabel>Tab 3</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
);

With these pages and components created, let's now configure and use AuthConnectProvider.

First, import AuthConnectProvider and PrivateRoute from auth-connect-react:

import {
AuthConnectProvider,
PrivateRoute,
} from "@ionic-enterprise/auth-react";

We will also need isPlatform from @ionic/react, as we will need to supply the provider with different values depending on the platform we are running in.

const platform = isPlatform("capacitor") ? "capacitor" : "web";
const redirectUri = isPlatform("capacitor")
? "com.ionic.auth-demo://callback"
: "http://localhost:8100/callback";
const logoutUrl = isPlatform("capacitor")
? "com.ionic.auth-demo://logout"
: "http://localhost:8100/logout";

To control the login flow, will use AuthConnectProvider to wrap our app's top level routing switch:

const AuthConnectContainer: React.FC = () => {
const location = useLocation();
return (
<AuthConnectProvider
checkSessionOnChange={location.pathname}
logLevel={"ERROR"}
authConfig={"auth0"}
platform={platform}
clientID={"[YOUR_AUTH0_CLIENT_ID]"}
discoveryUrl={"[YOUR_AUTH0_DISCOVERY_URL]"}
redirectUri={redirectUri}
scope={"openid offline_access email picture profile"}
audience={"[YOUR_AUTH0_AUDIENCE]"}
logoutUrl={logoutUrl}
iosWebView={"private"}
webAuthFlow={"PKCE"}
implicitLogin={"POPUP"}
loginPath={"/login"}
onLoginSuccess={(result) => console.log("Login Successful", { result })}
onLogoutSuccess={() => console.log("Logout Successful")}
>
<Switch>
<Route path="/login" component={Login} />
<Route path="/logout" component={Logout} />
<Route path="/callback" component={Callback} />
<PrivateRoute path="/tabs" component={TabController} initializingComponent={() => <div>...Private Route Loading...</div>} />
<Redirect from="/" to="/login" exact />
</Switch>
</AuthConnectProvider>
);
};

You will notice a new route type we are using here, <PrivateRoute />. PrivateRoute is a convenience component that you can use to protect routes and only allow the user to navigate to them if the user is authenticated. It takes the usual props a normal React Router Route component does, as well as some additional ones:

  • loginPath: The path to the login screen that will be redirected to if the user is not authenticated. Can also be supplied globally on the AuthConnectProvider. The loginPath is required either globally on AuthConnectProvider or on the individual PrivateRoute. If specified on both, the loginPath on PrivateRoute will be used.
  • initializingComponent: The component to display while Auth Connect is initializing and determining if the user is authenticated or not. Can also be assigned globally in AuthConnectProvider. When assigned globally, the initializingComponent provided on PrivateRoute will be ignored. If not provided here or on AuthConnectProvider, PrivateRoute will return null when initializing, which will display a blank screen for a brief moment. A loading spinner or some

For more details on the various auth config options that you can use with this provider, view the Auth0 Configuration guide.

Finally insert AuthConnectContainer into the App component:

const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<AuthConnectContainer />
</IonReactRouter>
</IonApp>
);

You are now ready to test the application! Start the application using ionic serve.

App Login Page

To test out user authentication, tap / click the "Login" button (in desktop browsers, make sure popup windows are not blocked), and then login with a user created on your Auth0 account.

Auth0 Login Screen

If everything works, you should be redirected to the TabController as a logged in user:

Logged in Tab Controller