Learn how to get started with Auth Connect by creating a sample project that uses @ionic/react
.
This tutorial starts with installing and configuring Auth Connect and moves onto achieving common use-cases: performing login/logout, checking the user's authentication status, obtaining tokens from Auth Connect, and storing tokens using Identity Vault.
_117import { AuthConnect, ProviderOptions, Auth0Provider, TokenType } from "@ionic-enterprise/auth";
_117import { isPlatform } from '@ionic/react';
_117import { PropsWithChildren, createContext, useState, useEffect, useContext } from 'react';
_117import { SessionVaultContext } from './SessionVaultProvider';
_117const isNative = isPlatform('hybrid');
_117const options: ProviderOptions = {
_117 audience: 'https://io.ionic.demo.ac',
_117 clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_117 discoveryUrl: 'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_117 logoutUrl: isNative ? 'msauth://login' : 'http://localhost:8100/login',
_117 redirectUri: isNative ? 'msauth://login' : 'http://localhost:8100/login',
_117 scope: 'openid offline_access email picture profile',
_117const setupAuthConnect = async (): Promise<void> => {
_117 return AuthConnect.setup({
_117 platform: isNative ? 'capacitor' : 'web',
_117 ios: { webView: 'private' },
_117 web: { uiMode: 'popup', authFlow: 'implicit' },
_117const provider = new Auth0Provider();
_117export const AuthContext = createContext<{
_117 isAuthenticated: boolean;
_117 getAccessToken: () => Promise<string | undefined>;
_117 getUserName: () => Promise<string | undefined>;
_117 login: () => Promise<void>;
_117 logout: () => Promise<void>;
_117 isAuthenticated: false,
_117 getAccessToken: () => { throw new Error('Method not implemented.'); },
_117 getUserName: () => { throw new Error('Method not implemented.'); },
_117 login: () => { throw new Error('Method not implemented.'); },
_117 logout: () => { throw new Error('Method not implemented.'); },
_117export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_117 const [isSetup, setIsSetup] = useState<boolean>(false);
_117 const { clearSession, getSession, setSession } = useContext(SessionVaultContext);
_117 const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
_117 const saveAuthResult = async (authResult: AuthResult | null): Promise<void> => {
_117 await setSession(authResult);
_117 setIsAuthenticated(true);
_117 await clearSession();
_117 setIsAuthenticated(false);
_117 const refreshAuth = async (authResult: AuthResult): Promise<AuthResult | null> => {
_117 let newAuthResult: AuthResult | null = null;
_117 if (await AuthConnect.isRefreshTokenAvailable(authResult)) {
_117 newAuthResult = await AuthConnect.refreshSession(provider, authResult);
_117 console.log('Error refreshing session.', err);
_117 return newAuthResult;
_117 const getAuthResult = async (): Promise<AuthResult | null> => {
_117 let authResult = await getSession();
_117 if (authResult && (await AuthConnect.isAccessTokenExpired(authResult))) {
_117 const newAuthResult = await refreshAuth(authResult);
_117 await saveAuthResult(newAuthResult);
_117 setIsAuthenticated(!!authResult);
_117 setupAuthConnect().then(() => setIsSetup(true));
_117 const getAccessToken = async (): Promise<string | undefined> => {
_117 const res = await getAuthResult();
_117 return res?.accessToken;
_117 const getUserName = async (): Promise<string | undefined> => {
_117 const res = await getAuthResult();
_117 const data = await AuthConnect.decodeToken<{ name: string }>(TokenType.id, res);
_117 const login = async (): Promise<void> => {
_117 const authResult = await AuthConnect.login(provider, options);
_117 await saveAuthResult(authResult);
_117 const logout = async (): Promise<void> => {
_117 const authResult = await getAuthResult();
_117 await AuthConnect.logout(provider, authResult);
_117 await saveAuthResult(null);
_117 <AuthContext.Provider value={{ isAuthenticated, getAccessToken, getUserName, login, logout }}>
_117 {isSetup && children}
_117 </AuthContext.Provider>
Generate the Application
Start by creating an Ionic React Tabbed Layout starter.
ionic start getting-started-ac-react-tabs --type=react
Change the app identifier
Ionic recommends changing the app identifier before adding any deployment platforms. If you want to change the app identifier after adding iOS or Android, you will have to update native artifacts.
_10import { CapacitorConfig } from "@capacitor/cli";
_10 appId: "io.ionic.gettingstartedacreact",
Remove React Strict Mode
Strict mode triggers useEffect
twice in development, remove it for this tutorial.
_19import React from "react";
Build the application then install and create the iOS and Android platforms.
Modify the build script
Modify the build
npm script to sync Capacitor with each build.
_24 "build": "react-scripts build && cap sync",
Install Auth Connect
In order to install Auth Connect, you will need to use ionic enterprise register
to register your product key. This will create a .npmrc
file containing the product key.
If you have already performed that step for your production application, you can just copy the .npmrc
file from your production project. Since this application is for learning purposes only, you don't need to obtain another key.
npm install @ionic-enterprise/auth
Auth Connect functionality will be exposed through a React Context.
Create AuthProvider.tsx
within a folder named src/providers
.
_20import { ProviderOptions } from "@ionic-enterprise/auth";
_20import { isPlatform } from "@ionic/react";
_20import { PropsWithChildren, createContext } from "react";
_20const isNative = isPlatform("hybrid");
_20const options: ProviderOptions = {
_20 scope: "openid offline_access",
_20 redirectUri: isNative ? "" : "",
_20 logoutUrl: isNative ? "" : "",
_20export const AuthContext = createContext<{}>({});
_20export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_20 return <AuthContext.Provider value={{}}>{children}</AuthContext.Provider>;
Don't forget to wrap IonReactRouter
with the new provider so the Context can be shared with the Tabs.
_78import { Redirect, Route } from "react-router-dom";
Auth Connect options
The options
object gets passed to the login()
function to help establish the authentication session.
Obtaining this information likely takes coordination with whomever administers the backend services.
You can use your own configuration for this step; however, we suggest starting with our configuration, get the application working, and then try your own configuration after that.
_21import { ProviderOptions } from "@ionic-enterprise/auth";
_21import { isPlatform } from "@ionic/react";
_21import { PropsWithChildren, createContext } from "react";
_21const isNative = isPlatform("hybrid");
_21const options: ProviderOptions = {
_21 audience: "https://io.ionic.demo.ac",
_21 clientId: "yLasZNUGkZ19DGEjTmAITBfGXzqbvd00",
_21 "https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration",
_21 logoutUrl: isNative ? "msauth://login" : "http://localhost:8100/login",
_21 redirectUri: isNative ? "msauth://login" : "http://localhost:8100/login",
_21 scope: "openid offline_access email picture profile",
_21export const AuthContext = createContext<{}>({});
_21export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_21 return <AuthContext.Provider value={{}}>{children}</AuthContext.Provider>;
Initialization
Auth Connect needs to be initialized before any functions can be used.
A flag in AuthProvider
will prevent components within IonReactRouter
from rendering until Auth Connect has initialized.
_40import { AuthConnect, ProviderOptions } from "@ionic-enterprise/auth";
_40import { isPlatform } from "@ionic/react";
_40import { PropsWithChildren, createContext } from "react";
_40const isNative = isPlatform("hybrid");
_40const options: ProviderOptions = {
_40 audience: "https://io.ionic.demo.ac",
_40 clientId: "yLasZNUGkZ19DGEjTmAITBfGXzqbvd00",
_40 "https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration",
_40 logoutUrl: isNative ? "msauth://login" : "http://localhost:8100/login",
_40 redirectUri: isNative ? "msauth://login" : "http://localhost:8100/login",
_40 scope: "openid offline_access email picture profile",
_40const setupAuthConnect = async (): Promise<void> => {
_40 return AuthConnect.setup({
_40 platform: isNative ? "capacitor" : "web",
_40 ios: { webView: "private" },
_40 web: { uiMode: "popup", authFlow: "implicit" },
_40export const AuthContext = createContext<{}>({});
_40export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_40 const [isSetup, setIsSetup] = useState<boolean>(false);
_40 setupAuthConnect().then(() => setIsSetup(true));
_40 <AuthContext.Provider value={{}}>
_40 {isSetup && children}
_40 </AuthContext.Provider>
The Provider
A provider object specifying details on communicating with the OIDC service is required.
Auth Connect offers several common providers out of the box: Auth0Provider
, AzureProvider
, CognitoProvider
, OktaProvider
, and OneLoginProvider
. You can also create your own provider, though doing so is beyond the scope of this tutorial.
_42import { AuthConnect, ProviderOptions, Auth0Provider } from "@ionic-enterprise/auth";
_42import { isPlatform } from "@ionic/react";
_42import { PropsWithChildren, createContext } from "react";
_42const isNative = isPlatform("hybrid");
_42const options: ProviderOptions = {
_42 audience: "https://io.ionic.demo.ac",
_42 clientId: "yLasZNUGkZ19DGEjTmAITBfGXzqbvd00",
_42 "https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration",
_42 logoutUrl: isNative ? "msauth://login" : "http://localhost:8100/login",
_42 redirectUri: isNative ? "msauth://login" : "http://localhost:8100/login",
_42 scope: "openid offline_access email picture profile",
_42const setupAuthConnect = async (): Promise<void> => {
_42 return AuthConnect.setup({
_42 platform: isNative ? "capacitor" : "web",
_42 ios: { webView: "private" },
_42 web: { uiMode: "popup", authFlow: "implicit" },
_42export const provider = new Auth0Provider();
_42export const AuthContext = createContext<{}>({});
_42export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_42 const [isSetup, setIsSetup] = useState<boolean>(false);
_42 setupAuthConnect().then(() => setIsSetup(true));
_42 <AuthContext.Provider value={{}}>
_42 {isSetup && children}
_42 </AuthContext.Provider>
Login and Logout
Login and logout are the two most fundamental operations in the authentication flow.
Login requires both the provider
and options
established above. Login resolves an AuthResult
if the operation succeeds. The AuthResult
contains auth tokens as well as some other information. This object needs to be passed to almost all other AuthConnect
functions; as such, it needs to be saved. The login()
call rejects with an error if the user cancels the login or if something else prevents the login to complete.
_52import { AuthConnect, ProviderOptions, Auth0Provider, AuthResult } from "@ionic-enterprise/auth";
_52import { isPlatform } from "@ionic/react";
_52import { PropsWithChildren, createContext } from "react";
_52const isNative = isPlatform("hybrid");
_52const options: ProviderOptions = {
_52 audience: "https://io.ionic.demo.ac",
_52 clientId: "yLasZNUGkZ19DGEjTmAITBfGXzqbvd00",
_52 "https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration",
_52 logoutUrl: isNative ? "msauth://login" : "http://localhost:8100/login",
_52 redirectUri: isNative ? "msauth://login" : "http://localhost:8100/login",
_52 scope: "openid offline_access email picture profile",
_52const setupAuthConnect = async (): Promise<void> => {
_52 return AuthConnect.setup({
_52 platform: isNative ? "capacitor" : "web",
_52 ios: { webView: "private" },
_52 web: { uiMode: "popup", authFlow: "implicit" },
_52export const provider = new Auth0Provider();
_52export const AuthContext = createContext<{
_52 login: () => Promise<void>;
_52 login: () => { throw new Error("Method not implemented."); }
_52export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_52 const [isSetup, setIsSetup] = useState<boolean>(false);
_52 const [authResult, setAuthResult] = useState<AuthResult | null>(null);
_52 setupAuthConnect().then(() => setIsSetup(true));
_52 const login = async (): Promise<void> => {
_52 const authResult = await AuthConnect.login(provider, options);
_52 setAuthResult(authResult);
_52 <AuthContext.Provider value={{ login }}>
_52 {isSetup && children}
_52 </AuthContext.Provider>
Logout requires the provider
as well as the AuthResult
object returned by the login()
call.
_61import { AuthConnect, ProviderOptions, Auth0Provider, AuthResult } from "@ionic-enterprise/auth";
_61import { isPlatform } from "@ionic/react";
_61import { PropsWithChildren, createContext } from "react";
_61const isNative = isPlatform("hybrid");
_61const options: ProviderOptions = {
_61 audience: "https://io.ionic.demo.ac",
_61 clientId: "yLasZNUGkZ19DGEjTmAITBfGXzqbvd00",
_61 "https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration",
_61 logoutUrl: isNative ? "msauth://login" : "http://localhost:8100/login",
_61 redirectUri: isNative ? "msauth://login" : "http://localhost:8100/login",
_61 scope: "openid offline_access email picture profile",
_61const setupAuthConnect = async (): Promise<void> => {
_61 return AuthConnect.setup({
_61 platform: isNative ? "capacitor" : "web",
_61 ios: { webView: "private" },
_61 web: { uiMode: "popup", authFlow: "implicit" },
_61export const provider = new Auth0Provider();
_61export const AuthContext = createContext<{
_61 login: () => Promise<void>;
_61 logout: () => Promise<void>;
_61 login: () => { throw new Error("Method not implemented."); },
_61 logout: () => { throw new Error("Method not implemented."); }
_61export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_61 const [isSetup, setIsSetup] = useState<boolean>(false);
_61 const [authResult, setAuthResult] = useState<AuthResult | null>(null);
_61 setupAuthConnect().then(() => setIsSetup(true));
_61 const login = async (): Promise<void> => {
_61 const authResult = await AuthConnect.login(provider, options);
_61 setAuthResult(authResult);
_61 const logout = async (): Promise<void> => {
_61 await AuthConnect.logout(provider, authResult);
_61 <AuthContext.Provider value={{ login, logout }}>
_61 {isSetup && children}
_61 </AuthContext.Provider>
To test these new functions, replace ExploreContainer
with "Login" and "Logout" buttons in src/pages/Tab1.tsx
.
Using Ionic's Auth0 provider, functionality can be tested with the following credentials:
- Email Address:
test@ionic.io
- Password:
Ion54321
_29import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
_29import ExploreContainer from '../components/ExploreContainer';
_29import { AuthContext } from '../providers/AuthProvider';
_29const Tab1: React.FC = () => {
_29 const { login, logout } = useContext(AuthContext);
_29 <IonTitle>Tab 1</IonTitle>
_29 <IonContent fullscreen>
_29 <IonHeader collapse="condense">
_29 <IonTitle size="large">Tab 1</IonTitle>
_29 <IonButton onClick={login}>Login</IonButton>
_29 <IonButton onClick={logout}>Logout</IonButton>
Login will fail when testing on a native device.
This occurs because the native device doesn't know which application(s) handle navigation to the msauth://
scheme (using Ionic's Auth0 provider). To register the application to handle the scheme, modify the build.gradle
and Info.plist
files as noted here.
Replace $AUTH_URL_SCHEME
with msauth
when using Ionic's Auth0 provider.
Determining the current auth status
_71import { AuthConnect, ProviderOptions, Auth0Provider, AuthResult } from "@ionic-enterprise/auth";
_71import { isPlatform } from "@ionic/react";
_71import { PropsWithChildren, createContext } from "react";
_71const isNative = isPlatform("hybrid");
_71const options: ProviderOptions = {
_71 audience: "https://io.ionic.demo.ac",
_71 clientId: "yLasZNUGkZ19DGEjTmAITBfGXzqbvd00",
_71 "https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration",
_71 logoutUrl: isNative ? "msauth://login" : "http://localhost:8100/login",
_71 redirectUri: isNative ? "msauth://login" : "http://localhost:8100/login",
_71 scope: "openid offline_access email picture profile",
_71const setupAuthConnect = async (): Promise<void> => {
_71 return AuthConnect.setup({
_71 platform: isNative ? "capacitor" : "web",
_71 ios: { webView: "private" },
_71 web: { uiMode: "popup", authFlow: "implicit" },
_71export const provider = new Auth0Provider();
_71export const AuthContext = createContext<{
_71 isAuthenticated: boolean;
_71 login: () => Promise<void>;
_71 logout: () => Promise<void>;
_71 isAuthenticated: false,
_71 login: () => { throw new Error("Method not implemented."); },
_71 logout: () => { throw new Error("Method not implemented."); }
_71export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_71 const [isSetup, setIsSetup] = useState<boolean>(false);
_71 const [authResult, setAuthResult] = useState<AuthResult | null>(null);
_71 const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
_71 const getAuthResult = async (): Promise<AuthResult | null> => {
_71 setIsAuthenticated(!!authResult);
_71 setupAuthConnect().then(() => setIsSetup(true));
_71 const login = async (): Promise<void> => {
_71 const authResult = await AuthConnect.login(provider, options);
_71 setAuthResult(authResult);
_71 setIsAuthenticated(true);
_71 const logout = async (): Promise<void> => {
_71 await AuthConnect.logout(provider, authResult);
_71 setIsAuthenticated(false);
_71 <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
_71 {isSetup && children}
_71 </AuthContext.Provider>
Users are shown both the login and logout buttons but you don't really know if the user is logged in or not. Let's change that.
A simple strategy to use is tracking the status via state, updating the value after calling certain AuthConnect
methods.
Ignore the extra complexity with the getAuthResult()
function -- we will expand on that as we go.
_44import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
_44import ExploreContainer from '../components/ExploreContainer';
_44import { AuthContext } from '../providers/AuthProvider';
_44const Tab1: React.FC = () => {
_44 const { isAuthenticated, login, logout } = useContext(AuthContext);
_44 const handleLogin = async () => {
_44 console.log("Error logging in:", err);
_44 const handleLogout = async () => {
_44 <IonTitle>Tab 1</IonTitle>
_44 <IonContent fullscreen>
_44 <IonHeader collapse="condense">
_44 <IonTitle size="large">Tab 1</IonTitle>
_44 {!isAuthenticated ? (
_44 <IonButton onClick={handleLogin}>Login</IonButton>
_44 <IonButton onClick={handleLogout}>Logout</IonButton>
Use isAuthenticated
to determine which button to display, depending on the current auth status.
Notice the try...catch
in the handleLogin()
method. Production apps should have some kind of handling here, but this tutorial will only log the fact.
At this point, you should see the "Login" button if you are not logged in and the "Logout" button if you are.
Get the tokens
_93import { AuthConnect, ProviderOptions, Auth0Provider, AuthResult, TokenType } from "@ionic-enterprise/auth";
_93import { isPlatform } from "@ionic/react";
_93import { PropsWithChildren, createContext } from "react";
_93const isNative = isPlatform("hybrid");
_93const options: ProviderOptions = {
_93 audience: "https://io.ionic.demo.ac",
_93 clientId: "yLasZNUGkZ19DGEjTmAITBfGXzqbvd00",
_93 "https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration",
_93 logoutUrl: isNative ? "msauth://login" : "http://localhost:8100/login",
_93 redirectUri: isNative ? "msauth://login" : "http://localhost:8100/login",
_93 scope: "openid offline_access email picture profile",
_93const setupAuthConnect = async (): Promise<void> => {
_93 return AuthConnect.setup({
_93 platform: isNative ? "capacitor" : "web",
_93 ios: { webView: "private" },
_93 web: { uiMode: "popup", authFlow: "implicit" },
_93export const provider = new Auth0Provider();
_93export const AuthContext = createContext<{
_93 isAuthenticated: boolean;
_93 getAccessToken: () => Promise<string | undefined>;
_93 getUserName: () => Promise<string | undefined>;
_93 login: () => Promise<void>;
_93 logout: () => Promise<void>;
_93 isAuthenticated: false,
_93 getAccessToken: () => { throw new Error("Method not implemented."); },
_93 getUserName: () => { throw new Error("Method not implemented."); },
_93 login: () => { throw new Error("Method not implemented."); },
_93 logout: () => { throw new Error("Method not implemented."); }
_93export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_93 const [isSetup, setIsSetup] = useState<boolean>(false);
_93 const [authResult, setAuthResult] = useState<AuthResult | null>(null);
_93 const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
_93 const getAuthResult = async (): Promise<AuthResult | null> => {
_93 setIsAuthenticated(!!authResult);
_93 setupAuthConnect().then(() => setIsSetup(true));
_93 const login = async (): Promise<void> => {
_93 const authResult = await AuthConnect.login(provider, options);
_93 setAuthResult(authResult);
_93 setIsAuthenticated(true);
_93 const logout = async (): Promise<void> => {
_93 await AuthConnect.logout(provider, authResult);
_93 setIsAuthenticated(false);
_93 const getAccessToken = async (): Promise<string | undefined> => {
_93 const res = await getAuthResult();
_93 return res?.accessToken;
_93 const getUserName = async (): Promise<string | undefined> => {
_93 const res = await getAuthResult();
_93 const data = await AuthConnect.decodeToken<{ name: string }>(
_93 <AuthContext.Provider value={{
_93 isAuthenticated, getAccessToken, getUserName, login, logout
_93 {isSetup && children}
_93 </AuthContext.Provider>
We can now log in and out, but what about getting the tokens that the OIDC provider gave us? That information is stored as part of the AuthResult
. Auth Connect provides methods to allow us to easily look at the contents of the tokens.
Note: the format and data stored in the ID token may change based on your provider and configuration. Check the documentation and configuration of your own provider for details.
These methods can be used wherever a specific token is required. For example, if you are accessing a backend API that requires you to include a bearer token, you can use getAccessToken()
to create an HTTP interceptor that adds the token to HTTP requests.
An interceptor isn't needed for this app, but as a challenge to you, update Tab1.tsx
to show the user's name when they are logged in. You could also display the access token if you want (though you'd never do that in a real app).
Refreshing the authentication session
_111import { AuthConnect, ProviderOptions, Auth0Provider } from "@ionic-enterprise/auth";
_111import { isPlatform } from "@ionic/react";
_111import { PropsWithChildren, createContext } from "react";
_111const isNative = isPlatform("hybrid");
_111const options: ProviderOptions = {
_111 audience: "https://io.ionic.demo.ac",
_111 clientId: "yLasZNUGkZ19DGEjTmAITBfGXzqbvd00",
_111 "https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration",
_111 logoutUrl: isNative ? "msauth://login" : "http://localhost:8100/login",
_111 redirectUri: isNative ? "msauth://login" : "http://localhost:8100/login",
_111 scope: "openid offline_access email picture profile",
_111const setupAuthConnect = async (): Promise<void> => {
_111 return AuthConnect.setup({
_111 platform: isNative ? "capacitor" : "web",
_111 ios: { webView: "private" },
_111 web: { uiMode: "popup", authFlow: "implicit" },
_111export const provider = new Auth0Provider();
_111export const AuthContext = createContext<{
_111 isAuthenticated: boolean;
_111 getAccessToken: () => Promise<string | undefined>;
_111 getUserName: () => Promise<string | undefined>;
_111 login: () => Promise<void>;
_111 logout: () => Promise<void>;
_111 isAuthenticated: false,
_111 getAccessToken: () => { throw new Error("Method not implemented."); },
_111 getUserName: () => { throw new Error("Method not implemented."); },
_111 login: () => { throw new Error("Method not implemented."); },
_111 logout: () => { throw new Error("Method not implemented."); }
_111export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_111 const [isSetup, setIsSetup] = useState<boolean>(false);
_111 const [authResult, setAuthResult] = useState<AuthResult | null>(null);
_111 const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
_111 const refreshAuth = async (authResult: AuthResult): Promise<AuthResult | null> => {
_111 let newAuthResult: AuthResult | null = null;
_111 if (await AuthConnect.isRefreshTokenAvailable(authResult)) {
_111 newAuthResult = await AuthConnect.refreshSession(provider, authResult);
_111 console.log("Error refreshing session.", err);
_111 return newAuthResult;
_111 const getAuthResult = async (): Promise<AuthResult | null> => {
_111 if (authResult && (await AuthConnect.isAccessTokenExpired(authResult))) {
_111 const newAuthResult = await refreshAuth(authResult);
_111 setAuthResult(newAuthResult);
_111 setIsAuthenticated(!!authResult);
_111 setupAuthConnect().then(() => setIsSetup(true));
_111 const login = async (): Promise<void> => {
_111 const authResult = await AuthConnect.login(provider, options);
_111 setAuthResult(authResult);
_111 setIsAuthenticated(true);
_111 const logout = async (): Promise<void> => {
_111 await AuthConnect.logout(provider, authResult);
_111 setAuthResult(null);
_111 setIsAuthenticated(false);
_111 const getAccessToken = async (): Promise<string | undefined> => {
_111 const res = await getAuthResult();
_111 return res?.accessToken;
_111 const getUserName = async (): Promise<string | undefined> => {
_111 const res = await getAuthResult();
_111 const data = await AuthConnect.decodeToken<{ name: string }>(
_111 <AuthContext.Provider value={{
_111 isAuthenticated, getAccessToken, getUserName, login, logout
_111 {isSetup && children}
_111 </AuthContext.Provider>
In a typical OIDC implementation, access tokens are very short lived. It is common to use a longer lived refresh token to obtain a new AuthResult
.
Add a function that performs a refresh, then modify getAuthResult()
to refresh the session when needed. Now anything using getAuthResult()
to get the current auth result will automatically trigger a refresh if needed.
Store the Auth Result
So far the AuthResult
has been stored in a local state variable, which has a couple of disadvantages:
- Tokens could show up in a stack trace.
- Tokens do not survive a browser refresh or application restart.
There are several options we could use to store the AuthResult, but one that handles persistence as well as storing the data in a secure location on native devices is Ionic Identity Vault.
For our application, we will install Identity Vault and use it in "Secure Storage" mode to store the tokens.
npm install @ionic-enterprise/identity-vault
Create a Vault factory
_10import { BrowserVault, IdentityVaultConfig, Vault } from "@ionic-enterprise/identity-vault";
_10import { isPlatform } from "@ionic/react";
_10const createVault = (config: IdentityVaultConfig): Vault | BrowserVault => {
_10 return isPlatform("hybrid") ? new Vault(config) : new BrowserVault(config);
Create SessionVaultProvider.tsx
within src/providers
. In this file setup a factory that builds either the actual Vault - if we are on a device - or a browser-based "Vault" suitable for development in the browser.
This provides us with a secure Vault on device, or a fallback Vault that allows us to keep using our browser-based development flow.
Set up a React context
_53import { BrowserVault, IdentityVaultConfig, Vault } from "@ionic-enterprise/identity-vault";
_53import { isPlatform } from "@ionic/react";
_53const createVault = (config: IdentityVaultConfig): Vault | BrowserVault => {
_53 return isPlatform("hybrid") ? new Vault(config) : new BrowserVault(config);
_53const key = 'auth-result';
_53const vault = createVault({
_53 key: 'io.ionic.gettingstartedacreact',
_53 type: VaultType.SecureStorage,
_53 deviceSecurityType: DeviceSecurityType.None,
_53 lockAfterBackgrounded: 5000,
_53 shouldClearVaultAfterTooManyFailedAttempts: true,
_53 customPasscodeInvalidUnlockAttempts: 2,
_53 unlockVaultOnLoad: false,
_53export const SessionVaultContext = createContext<{
_53 clearSession: () => Promise<void>;
_53 getSession: () => Promise<AuthResult | null>;
_53 setSession: (value?: AuthResult) => Promise<void>;
_53 clearSession: () => {
_53 throw new Error('Method not implemented.');
_53 throw new Error('Method not implemented.');
_53 throw new Error('Method not implemented.');
_53export const SessionVaultProvider: React.FC<PropsWithChildren> = ({ children }) => {
_53 const clearSession = (): Promise<void> => {
_53 return vault.clear();
_53 const getSession = (): Promise<AuthResult | null> => {
_53 return vault.getValue<AuthResult>(key);
_53 const setSession = (value?: AuthResult): Promise<void> => {
_53 return vault.setValue(key, value);
_53 <SessionVaultContext.Provider value={{ clearSession, getSession, setSession }}>
_53 </SessionVaultContext.Provider>
Like AuthProvider
, a React Context will be used to expose functionality related to managing the authentication result.
Don't forget to wrap IonReactRouter
with the new provider so the Context can be shared with the Tabs.
_80import { Redirect, Route } from "react-router-dom";
_80 </SessionVaultProvider>
Modify the Auth context
_122import { AuthConnect, ProviderOptions, Auth0Provider, TokenType } from "@ionic-enterprise/auth";
_122import { isPlatform } from "@ionic/react";
_122import { PropsWithChildren, createContext } from "react";
_122import { SessionVaultContext } from './SessionVaultProvider';
_122const isNative = isPlatform("hybrid");
_122const options: ProviderOptions = {
_122 audience: "https://io.ionic.demo.ac",
_122 clientId: "yLasZNUGkZ19DGEjTmAITBfGXzqbvd00",
_122 "https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration",
_122 logoutUrl: isNative ? "msauth://login" : "http://localhost:8100/login",
_122 redirectUri: isNative ? "msauth://login" : "http://localhost:8100/login",
_122 scope: "openid offline_access email picture profile",
_122const setupAuthConnect = async (): Promise<void> => {
_122 return AuthConnect.setup({
_122 platform: isNative ? "capacitor" : "web",
_122 ios: { webView: "private" },
_122 web: { uiMode: "popup", authFlow: "implicit" },
_122export const provider = new Auth0Provider();
_122export const AuthContext = createContext<{
_122 isAuthenticated: boolean;
_122 getAccessToken: () => Promise<string | undefined>;
_122 getUserName: () => Promise<string | undefined>;
_122 login: () => Promise<void>;
_122 logout: () => Promise<void>;
_122 isAuthenticated: false,
_122 getAccessToken: () => { throw new Error("Method not implemented."); },
_122 getUserName: () => { throw new Error("Method not implemented."); },
_122 login: () => { throw new Error("Method not implemented."); },
_122 logout: () => { throw new Error("Method not implemented."); }
_122export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
_122 const [isSetup, setIsSetup] = useState<boolean>(false);
_122 const { clearSession, getSession, setSession } = useContext(SessionVaultContext);
_122 const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
_122 const saveAuthResult = async (authResult: AuthResult | null): Promise<void> => {
_122 await setSession(authResult);
_122 setIsAuthenticated(true);
_122 await clearSession();
_122 setIsAuthenticated(false);
_122 const refreshAuth = async (authResult: AuthResult): Promise<AuthResult | null> => {
_122 let newAuthResult: AuthResult | null = null;
_122 if (await AuthConnect.isRefreshTokenAvailable(authResult)) {
_122 newAuthResult = await AuthConnect.refreshSession(provider, authResult);
_122 console.log("Error refreshing session.", err);
_122 return newAuthResult;
_122 const getAuthResult = async (): Promise<AuthResult | null> => {
_122 let authResult = await getSession();
_122 if (authResult && (await AuthConnect.isAccessTokenExpired(authResult))) {
_122 const newAuthResult = await refreshAuth(authResult);
_122 await saveAuthResult(newAuthResult);
_122 setIsAuthenticated(!!authResult);
_122 setupAuthConnect().then(() => setIsSetup(true));
_122 const login = async (): Promise<void> => {
_122 const authResult = await AuthConnect.login(provider, options);
_122 await saveAuthResult(authResult);
_122 const logout = async (): Promise<void> => {
_122 await AuthConnect.logout(provider, authResult);
_122 await saveAuthResult(authResult);
_122 const getAccessToken = async (): Promise<string | undefined> => {
_122 const res = await getAuthResult();
_122 return res?.accessToken;
_122 const getUserName = async (): Promise<string | undefined> => {
_122 const res = await getAuthResult();
_122 const data = await AuthConnect.decodeToken<{ name: string }>(
_122 <AuthContext.Provider value={{
_122 isAuthenticated, getAccessToken, getUserName, login, logout
_122 {isSetup && children}
_122 </AuthContext.Provider>
The goal is to no longer store the auth result in a session variable. Instead, we will use the session Vault to store the result and retrieve it as needed.
You should now be able to refresh the app and have a persistent session. The tutorial is now complete!