Skip to main content
Version: 6.0

Getting Started with Auth Connect

Generate the Application

Before we explore the use of Auth Connect, we need to scaffold an application. In this section, we will generate an @ionic/vue tabs based application, perform some basic configuration, and add the iOS and Android platforms.

Terminal

_10
ionic start getting-started-ac tabs --type=vue

Use the Ionic CLI to generate the application.

Terminal

_10
ionic start getting-started-ac tabs --type=vue
_10
cd getting-started-ac

Change directory into the newly generated project.

Terminal
capacitor.config.ts

_12
import { CapacitorConfig } from '@capacitor/cli';
_12
_12
const config: CapacitorConfig = {
_12
appId: 'io.ionic.gettingstartedac',
_12
appName: 'getting-started-ac',
_12
webDir: 'dist',
_12
server: {
_12
androidScheme: 'https',
_12
},
_12
};
_12
_12
export default config;

Change the appId to be something unique. The appId is used as the bundle ID / application ID. Therefore it should be a string that is unique to your organization and application. We will use io.ionic.gettingstartedac for this application.

It is best to do this before adding the iOS and Android platforms to ensure they are setup properly from the start.

Terminal
capacitor.config.ts

_10
ionic start getting-started-ac tabs --type=vue
_10
cd getting-started-ac
_10
npm run build
_10
ionic cap add android
_10
ionic cap add ios

Build the application and install the platforms.

Terminal
capacitor.config.ts
package.json

_15
{
_15
"name": "getting-started-ac",
_15
"version": "0.0.1",
_15
"author": "Ionic Framework",
_15
"homepage": "https://ionicframework.com/",
_15
"scripts": {
_15
"dev": "vite",
_15
"build": "vue-tsc && vite build && cap sync",
_15
"preview": "vite preview",
_15
"test:e2e": "cypress run",
_15
"test:unit": "vitest",
_15
"lint": "eslint"
_15
},
_15
...
_15
}

We should do a cap sync with each build. This ensures our native projects remain up to date. Change the scripts in package.json to do this.

Terminal
capacitor.config.ts
package.json
vite.config.ts

_21
import legacy from '@vitejs/plugin-legacy';
_21
import vue from '@vitejs/plugin-vue';
_21
import path from 'path';
_21
import { defineConfig } from 'vite';
_21
_21
// https://vitejs.dev/config/
_21
export default defineConfig({
_21
plugins: [vue(), legacy()],
_21
resolve: {
_21
alias: {
_21
'@': path.resolve(__dirname, './src'),
_21
},
_21
},
_21
server: {
_21
port: 8100,
_21
},
_21
test: {
_21
globals: true,
_21
environment: 'jsdom',
_21
},
_21
});

Modify the vite.config.ts file to ensure that the development server (npm run dev) uses port 8100.

Use the Ionic CLI to generate the application.

Change directory into the newly generated project.

Change the appId to be something unique. The appId is used as the bundle ID / application ID. Therefore it should be a string that is unique to your organization and application. We will use io.ionic.gettingstartedac for this application.

It is best to do this before adding the iOS and Android platforms to ensure they are setup properly from the start.

Build the application and install the platforms.

We should do a cap sync with each build. This ensures our native projects remain up to date. Change the scripts in package.json to do this.

Modify the vite.config.ts file to ensure that the development server (npm run dev) uses port 8100.

Terminal

_10
ionic start getting-started-ac tabs --type=vue

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.

You can now install Auth Connect and sync the platforms:

Terminal

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

Create the Authentication Composable

All interaction with Auth Connect will be abstracted into an Authentication Composable. Create a src/composables directory. Then create a src/composables/authentication.ts file containing the shell of our composable.

src/composables/authentication.ts

_10
export const useAuthentication = () => ({});

Setup and Initialization

Before we use Auth Connect, we need to make sure that it is properly set up and initialized.

src/composables/authentication.ts
src/main.ts

_10
export const useAuthentication = () => ({});

We will build the composable up to perform the setup and initialization required by Auth Connect.

src/composables/authentication.ts
src/main.ts

_10
import { Capacitor } from '@capacitor/core';
_10
_10
const isNative = Capacitor.isNativePlatform();
_10
_10
export const useAuthentication = () => ({});

Auth Connect needs a slightly different configuration between mobile and web, so we need to know in which context we are currently running.

src/composables/authentication.ts
src/main.ts

_10
import { Capacitor } from '@capacitor/core';
_10
import { Auth0Provider } from '@ionic-enterprise/auth';
_10
_10
const isNative = Capacitor.isNativePlatform();
_10
const provider = new Auth0Provider();
_10
_10
export const useAuthentication = () => ({});

For this tutorial, we are using Auth0 as the authentication vendor. We need to create an Auth0Provider to help Auth Connect with the communication with Auth0.

src/composables/authentication.ts
src/main.ts

_21
import { Capacitor } from '@capacitor/core';
_21
import { Auth0Provider, ProviderOptions } from '@ionic-enterprise/auth';
_21
_21
const isNative = Capacitor.isNativePlatform();
_21
const provider = new Auth0Provider();
_21
_21
const authOptions: ProviderOptions = {
_21
audience: 'https://io.ionic.demo.ac',
_21
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_21
discoveryUrl:
_21
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_21
logoutUrl: isNative
_21
? 'io.ionic.acdemo://auth-action-complete'
_21
: 'http://localhost:8100/auth-action-complete',
_21
redirectUri: isNative
_21
? 'io.ionic.acdemo://auth-action-complete'
_21
: 'http://localhost:8100/auth-action-complete',
_21
scope: 'openid offline_access email picture profile',
_21
};
_21
_21
export const useAuthentication = () => ({});

Auth Connect needs to know how to communicate with our authentication vendor. You will likely need to get this information from the team that manages your cloud infrastructure.

src/composables/authentication.ts
src/main.ts

_38
import { Capacitor } from '@capacitor/core';
_38
import {
_38
Auth0Provider,
_38
AuthConnect,
_38
ProviderOptions,
_38
} from '@ionic-enterprise/auth';
_38
_38
const isNative = Capacitor.isNativePlatform();
_38
const provider = new Auth0Provider();
_38
_38
const authOptions: ProviderOptions = {
_38
audience: 'https://io.ionic.demo.ac',
_38
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_38
discoveryUrl:
_38
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_38
logoutUrl: isNative
_38
? 'io.ionic.acdemo://auth-action-complete'
_38
: 'http://localhost:8100/auth-action-complete',
_38
redirectUri: isNative
_38
? 'io.ionic.acdemo://auth-action-complete'
_38
: 'http://localhost:8100/auth-action-complete',
_38
scope: 'openid offline_access email picture profile',
_38
};
_38
_38
export const useAuthentication = () => ({
_38
initializeAuthentication: async (): Promise<void> =>
_38
AuthConnect.setup({
_38
platform: isNative ? 'capacitor' : 'web',
_38
logLevel: 'DEBUG',
_38
ios: {
_38
webView: 'private',
_38
},
_38
web: {
_38
uiMode: 'popup',
_38
authFlow: 'PKCE',
_38
},
_38
}),
_38
});

We need to perform a one-time setup with Auth Connect. Please refer to the documentation if you have any questions about the individual properties. We will start here with a simple set up that is good for development.

src/composables/authentication.ts
src/main.ts

_34
import { createApp } from 'vue';
_34
import App from './App.vue';
_34
import router from './router';
_34
_34
import { IonicVue } from '@ionic/vue';
_34
_34
/* Core CSS required for Ionic components to work properly */
_34
import '@ionic/vue/css/core.css';
_34
_34
/* Basic CSS for apps built with Ionic */
_34
import '@ionic/vue/css/normalize.css';
_34
import '@ionic/vue/css/structure.css';
_34
import '@ionic/vue/css/typography.css';
_34
_34
/* Optional CSS utils that can be commented out */
_34
import '@ionic/vue/css/padding.css';
_34
import '@ionic/vue/css/float-elements.css';
_34
import '@ionic/vue/css/text-alignment.css';
_34
import '@ionic/vue/css/text-transformation.css';
_34
import '@ionic/vue/css/flex-utils.css';
_34
import '@ionic/vue/css/display.css';
_34
_34
/* Theme variables */
_34
import '@ionic/vue/css/palettes/dark.system.css';
_34
import './theme/variables.css';
_34
_34
import { useAuthentication } from '@/composables/authentication';
_34
const { initializeAuthentication } = useAuthentication();
_34
_34
initializeAuthentication().then(async () => {
_34
const app = createApp(App).use(IonicVue).use(router);
_34
await router.isReady();
_34
app.mount('#app');
_34
});

Modify src/main.ts to ensure Auth Connect is initialized prior to creating and mounting the application.

We will build the composable up to perform the setup and initialization required by Auth Connect.

Auth Connect needs a slightly different configuration between mobile and web, so we need to know in which context we are currently running.

For this tutorial, we are using Auth0 as the authentication vendor. We need to create an Auth0Provider to help Auth Connect with the communication with Auth0.

Auth Connect needs to know how to communicate with our authentication vendor. You will likely need to get this information from the team that manages your cloud infrastructure.

We need to perform a one-time setup with Auth Connect. Please refer to the documentation if you have any questions about the individual properties. We will start here with a simple set up that is good for development.

Modify src/main.ts to ensure Auth Connect is initialized prior to creating and mounting the application.

src/composables/authentication.ts
src/main.ts

_10
export const useAuthentication = () => ({});

Create the auth-action-complete Page

Note that the logoutUrl and redirectUri properties are using the /auth-action-complete route. Create a page for the route. The page does not have to do much. We will just display a spinner in case someone sees it momentarily.

src/views/AuthActionCompletePage.vue

_23
<template>
_23
<ion-content class="main-content">
_23
<div class="container">
_23
<ion-spinner name="dots"></ion-spinner>
_23
</div>
_23
</ion-content>
_23
</template>
_23
_23
<script setup lang="ts">
_23
import { IonContent, IonSpinner } from '@ionic/vue';
_23
</script>
_23
_23
<style scoped>
_23
.container {
_23
text-align: center;
_23
_23
position: absolute;
_23
left: 0;
_23
right: 0;
_23
top: 50%;
_23
transform: translateY(-50%);
_23
}
_23
</style>

Be sure to add the route in the router setup.

router/index.ts

_43
import { createRouter, createWebHistory } from '@ionic/vue-router';
_43
import { RouteRecordRaw } from 'vue-router';
_43
import TabsPage from '../views/TabsPage.vue';
_43
_43
const routes: Array<RouteRecordRaw> = [
_43
{
_43
path: '/',
_43
redirect: '/tabs/tab1',
_43
},
_43
component: () => import('@/views/AuthActionCompletePage.vue'),

Handling the Authentication Flow

Auth Connect is now properly set up and initialized. We can move on to creating the basic log in and log out flow. Within this flow, an AuthResult is obtained during log in that represents our authentication session. So long as we have an AuthResult object, we have an authentication session. The AuthResult is no longer valid after the user logs out.

Login and Logout

We begin by creating the login() and logout() functions.

src/composables/authentication.ts

_40
import { Capacitor } from '@capacitor/core';
_40
import {
_40
Auth0Provider,
_40
AuthConnect,
_40
AuthResult,
_40
ProviderOptions,
_40
} from '@ionic-enterprise/auth';
_40
_40
const isNative = Capacitor.isNativePlatform();
_40
const provider = new Auth0Provider();
_40
let authResult: AuthResult | null = null;
_40
_40
const authOptions: ProviderOptions = {
_40
audience: 'https://io.ionic.demo.ac',
_40
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_40
discoveryUrl:
_40
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_40
logoutUrl: isNative
_40
? 'io.ionic.acdemo://auth-action-complete'
_40
: 'http://localhost:8100/auth-action-complete',
_40
redirectUri: isNative
_40
? 'io.ionic.acdemo://auth-action-complete'
_40
: 'http://localhost:8100/auth-action-complete',
_40
scope: 'openid offline_access email picture profile',
_40
};
_40
_40
export const useAuthentication = () => ({
_40
initializeAuthentication: async (): Promise<void> =>
_40
AuthConnect.setup({
_40
platform: isNative ? 'capacitor' : 'web',
_40
logLevel: 'DEBUG',
_40
ios: {
_40
webView: 'private',
_40
},
_40
web: {
_40
uiMode: 'popup',
_40
authFlow: 'PKCE',
_40
},
_40
}),
_40
});

The AuthConnect.login() call resolves an AuthResult if the operation succeeds. The AuthResult contains the auth tokens as well as some other information. This object needs to be passed to almost all other Auth Connect functions. As such, it needs to be saved. We will store it in our authentication composable for now.

src/composables/authentication.ts

_43
import { Capacitor } from '@capacitor/core';
_43
import {
_43
Auth0Provider,
_43
AuthConnect,
_43
AuthResult,
_43
ProviderOptions,
_43
} from '@ionic-enterprise/auth';
_43
_43
const isNative = Capacitor.isNativePlatform();
_43
const provider = new Auth0Provider();
_43
let authResult: AuthResult | null = null;
_43
_43
const authOptions: ProviderOptions = {
_43
audience: 'https://io.ionic.demo.ac',
_43
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_43
discoveryUrl:
_43
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_43
logoutUrl: isNative
_43
? 'io.ionic.acdemo://auth-action-complete'
_43
: 'http://localhost:8100/auth-action-complete',
_43
redirectUri: isNative
_43
? 'io.ionic.acdemo://auth-action-complete'
_43
: 'http://localhost:8100/auth-action-complete',
_43
scope: 'openid offline_access email picture profile',
_43
};
_43
_43
export const useAuthentication = () => ({
_43
initializeAuthentication: async (): Promise<void> =>
_43
AuthConnect.setup({
_43
platform: isNative ? 'capacitor' : 'web',
_43
logLevel: 'DEBUG',
_43
ios: {
_43
webView: 'private',
_43
},
_43
web: {
_43
uiMode: 'popup',
_43
authFlow: 'PKCE',
_43
},
_43
}),
_43
login: async (): Promise<void> => {
_43
authResult = await AuthConnect.login(provider, authOptions);
_43
},
_43
});

For the login(), we need to pass both the provider and the options we established earlier.

src/composables/authentication.ts

_49
import { Capacitor } from '@capacitor/core';
_49
import {
_49
Auth0Provider,
_49
AuthConnect,
_49
AuthResult,
_49
ProviderOptions,
_49
} from '@ionic-enterprise/auth';
_49
_49
const isNative = Capacitor.isNativePlatform();
_49
const provider = new Auth0Provider();
_49
let authResult: AuthResult | null = null;
_49
_49
const authOptions: ProviderOptions = {
_49
audience: 'https://io.ionic.demo.ac',
_49
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_49
discoveryUrl:
_49
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_49
logoutUrl: isNative
_49
? 'io.ionic.acdemo://auth-action-complete'
_49
: 'http://localhost:8100/auth-action-complete',
_49
redirectUri: isNative
_49
? 'io.ionic.acdemo://auth-action-complete'
_49
: 'http://localhost:8100/auth-action-complete',
_49
scope: 'openid offline_access email picture profile',
_49
};
_49
_49
export const useAuthentication = () => ({
_49
initializeAuthentication: async (): Promise<void> =>
_49
AuthConnect.setup({
_49
platform: isNative ? 'capacitor' : 'web',
_49
logLevel: 'DEBUG',
_49
ios: {
_49
webView: 'private',
_49
},
_49
web: {
_49
uiMode: 'popup',
_49
authFlow: 'PKCE',
_49
},
_49
}),
_49
login: async (): Promise<void> => {
_49
authResult = await AuthConnect.login(provider, authOptions);
_49
},
_49
logout: async (): Promise<void> => {
_49
if (authResult) {
_49
await AuthConnect.logout(provider, authResult);
_49
authResult = null;
_49
}
_49
},
_49
});

For the logout(), when calling Auth Connect we need to pass the provider as well as the AuthResult we established with the login().

The AuthConnect.login() call resolves an AuthResult if the operation succeeds. The AuthResult contains the auth tokens as well as some other information. This object needs to be passed to almost all other Auth Connect functions. As such, it needs to be saved. We will store it in our authentication composable for now.

For the login(), we need to pass both the provider and the options we established earlier.

For the logout(), when calling Auth Connect we need to pass the provider as well as the AuthResult we established with the login().

src/composables/authentication.ts

_40
import { Capacitor } from '@capacitor/core';
_40
import {
_40
Auth0Provider,
_40
AuthConnect,
_40
AuthResult,
_40
ProviderOptions,
_40
} from '@ionic-enterprise/auth';
_40
_40
const isNative = Capacitor.isNativePlatform();
_40
const provider = new Auth0Provider();
_40
let authResult: AuthResult | null = null;
_40
_40
const authOptions: ProviderOptions = {
_40
audience: 'https://io.ionic.demo.ac',
_40
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_40
discoveryUrl:
_40
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_40
logoutUrl: isNative
_40
? 'io.ionic.acdemo://auth-action-complete'
_40
: 'http://localhost:8100/auth-action-complete',
_40
redirectUri: isNative
_40
? 'io.ionic.acdemo://auth-action-complete'
_40
: 'http://localhost:8100/auth-action-complete',
_40
scope: 'openid offline_access email picture profile',
_40
};
_40
_40
export const useAuthentication = () => ({
_40
initializeAuthentication: async (): Promise<void> =>
_40
AuthConnect.setup({
_40
platform: isNative ? 'capacitor' : 'web',
_40
logLevel: 'DEBUG',
_40
ios: {
_40
webView: 'private',
_40
},
_40
web: {
_40
uiMode: 'popup',
_40
authFlow: 'PKCE',
_40
},
_40
}),
_40
});

Hook Up the Login and Logout

We can use the first tab of our application to test the login() and logout() functions.

src/views/Tab1Page.vue

_29
<template>
_29
<ion-page>
_29
<ion-header>
_29
<ion-toolbar>
_29
<ion-title>Tab 1</ion-title>
_29
</ion-toolbar>
_29
</ion-header>
_29
<ion-content :fullscreen="true">
_29
<ion-header collapse="condense">
_29
<ion-toolbar>
_29
<ion-title size="large">Tab 1</ion-title>
_29
</ion-toolbar>
_29
</ion-header>
_29
_29
<ExploreContainer name="Tab 1 page" />
_29
</ion-content>
_29
</ion-page>
_29
</template>
_29
_29
<script setup lang="ts">
_29
import {
_29
IonPage,
_29
IonHeader,
_29
IonToolbar,
_29
IonTitle,
_29
IonContent,
_29
} from '@ionic/vue';
_29
import ExploreContainer from '@/components/ExploreContainer.vue';
_29
</script>

Currently, the Tab1Page contains the default skeleton code.

src/views/Tab1Page.vue

_32
<template>
_32
<ion-page>
_32
<ion-header>
_32
<ion-toolbar>
_32
<ion-title>Tab 1</ion-title>
_32
</ion-toolbar>
_32
</ion-header>
_32
<ion-content :fullscreen="true">
_32
<ion-header collapse="condense">
_32
<ion-toolbar>
_32
<ion-title size="large">Tab 1</ion-title>
_32
</ion-toolbar>
_32
</ion-header>
_32
_32
<ExploreContainer name="Tab 1 page" />
_32
</ion-content>
_32
</ion-page>
_32
</template>
_32
_32
<script setup lang="ts">
_32
import {
_32
IonPage,
_32
IonHeader,
_32
IonToolbar,
_32
IonTitle,
_32
IonContent,
_32
} from '@ionic/vue';
_32
import ExploreContainer from '@/components/ExploreContainer.vue';
_32
import { useAuthentication } from '@/composables/authentication';
_32
_32
const { login, logout } = useAuthentication();
_32
</script>

Import our the useAuthentication composable and destructure the login() and logout() functions.

src/views/Tab1Page.vue

_40
<template>
_40
<ion-page>
_40
<ion-header>
_40
<ion-toolbar>
_40
<ion-title>Tab 1</ion-title>
_40
</ion-toolbar>
_40
</ion-header>
_40
<ion-content :fullscreen="true">
_40
<ion-header collapse="condense">
_40
<ion-toolbar>
_40
<ion-title size="large">Tab 1</ion-title>
_40
</ion-toolbar>
_40
</ion-header>
_40
_40
<ExploreContainer name="Tab 1 page" />
_40
</ion-content>
_40
</ion-page>
_40
</template>
_40
_40
<script setup lang="ts">
_40
import {
_40
IonPage,
_40
IonHeader,
_40
IonToolbar,
_40
IonTitle,
_40
IonContent,
_40
} from '@ionic/vue';
_40
import ExploreContainer from '@/components/ExploreContainer.vue';
_40
import { useAuthentication } from '@/composables/authentication';
_40
_40
const { login, logout } = useAuthentication();
_40
_40
const loginClicked = async (): Promise<void> => {
_40
await login();
_40
};
_40
_40
const logoutClicked = async (): Promise<void> => {
_40
await logout();
_40
};
_40
</script>

Create loginClicked() and logoutClicked() functions that we can bind to in our template.

src/views/Tab1Page.vue

_41
<template>
_41
<ion-page>
_41
<ion-header>
_41
<ion-toolbar>
_41
<ion-title>Tab 1</ion-title>
_41
</ion-toolbar>
_41
</ion-header>
_41
<ion-content :fullscreen="true">
_41
<ion-header collapse="condense">
_41
<ion-toolbar>
_41
<ion-title size="large">Tab 1</ion-title>
_41
</ion-toolbar>
_41
</ion-header>
_41
_41
<ion-button @click="loginClicked">Login</ion-button>
_41
<ion-button @click="logoutClicked">Logout</ion-button>
_41
</ion-content>
_41
</ion-page>
_41
</template>
_41
_41
<script setup lang="ts">
_41
import {
_41
IonButton,
_41
IonPage,
_41
IonHeader,
_41
IonToolbar,
_41
IonTitle,
_41
IonContent,
_41
} from '@ionic/vue';
_41
import { useAuthentication } from '@/composables/authentication';
_41
_41
const { login, logout } = useAuthentication();
_41
_41
const loginClicked = async (): Promise<void> => {
_41
await login();
_41
};
_41
_41
const logoutClicked = async (): Promise<void> => {
_41
await logout();
_41
};
_41
</script>

Replace the ExploreContainer with login and logout buttons.

Currently, the Tab1Page contains the default skeleton code.

Import our the useAuthentication composable and destructure the login() and logout() functions.

Create loginClicked() and logoutClicked() functions that we can bind to in our template.

Replace the ExploreContainer with login and logout buttons.

src/views/Tab1Page.vue

_29
<template>
_29
<ion-page>
_29
<ion-header>
_29
<ion-toolbar>
_29
<ion-title>Tab 1</ion-title>
_29
</ion-toolbar>
_29
</ion-header>
_29
<ion-content :fullscreen="true">
_29
<ion-header collapse="condense">
_29
<ion-toolbar>
_29
<ion-title size="large">Tab 1</ion-title>
_29
</ion-toolbar>
_29
</ion-header>
_29
_29
<ExploreContainer name="Tab 1 page" />
_29
</ion-content>
_29
</ion-page>
_29
</template>
_29
_29
<script setup lang="ts">
_29
import {
_29
IonPage,
_29
IonHeader,
_29
IonToolbar,
_29
IonTitle,
_29
IonContent,
_29
} from '@ionic/vue';
_29
import ExploreContainer from '@/components/ExploreContainer.vue';
_29
</script>

Test this in the web using the following credentials:

  • email: test@ionic.io
  • password: Ion54321

At this point if we press the Login button, a tab should open where we can log in using Auth0. This tab will close after we log in. When we press the logout button a tab will briefly open to perform the logout and then automatically close.

Note that if you press the Login button while already logged in the login tab is closed immediately. This is expected behavior.

Configure the Native Projects

Login and logout are working in your web browser. Build your application for mobile and try to run them there. You can use an emulator or an actual device for this test.

Terminal

_10
npm run build
_10
npx cap open android
_10
npx cap open ios

On Android you are not returned to the application after logging in. You seem to be stuck at the Auth0 login page. On iOS you get an invalid URL error after successfully logging in on Auth0.

The problem is that on mobile we are deep-linking back into our application using io.ionic.acdemo://auth-action-complete. We have not registered that scheme with the OS so it does not know to deep-link back to our application. We will set that up now.

For Android, modify the android/variables.gradle file to include the AUTH_URL_SCHEME value:

app/variables.gradle

_17
ext {
_17
minSdkVersion = 22
_17
compileSdkVersion = 34
_17
targetSdkVersion = 34
_17
AUTH_URL_SCHEME = 'io.ionic.acdemo'

For iOS, add a CFBundleURLTypes section to the ios/App/App/Info.plist file:

App/App/Info.plist

_58
<?xml version="1.0" encoding="UTF-8"?>
_58
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
_58
<plist version="1.0">
_58
<dict>
_58
<key>CFBundleDevelopmentRegion</key>
_58
<string>en</string>
_58
<key>CFBundleDisplayName</key>
_58
<string>getting-started-ac</string>
_58
<key>CFBundleExecutable</key>
_58
<string>io.ionic.acdemo</string>

Re-run the application from Xcode and Android Studio. You should now be able to perform the authentication properly on the mobile applications.

Managing the Authentication Session

Determine if Authenticated

We can log in and we can log out, but it is hard to tell what our current authentication state is. Let's fix that now.

src/composables/authentication.ts
src/views/Tab1Page.vue

_53
import { Capacitor } from '@capacitor/core';
_53
import {
_53
Auth0Provider,
_53
AuthConnect,
_53
AuthResult,
_53
ProviderOptions,
_53
} from '@ionic-enterprise/auth';
_53
_53
const isNative = Capacitor.isNativePlatform();
_53
const provider = new Auth0Provider();
_53
const authOptions: ProviderOptions = {
_53
audience: 'https://io.ionic.demo.ac',
_53
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_53
discoveryUrl:
_53
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_53
logoutUrl: isNative
_53
? 'io.ionic.acdemo://auth-action-complete'
_53
: 'http://localhost:8100/auth-action-complete',
_53
redirectUri: isNative
_53
? 'io.ionic.acdemo://auth-action-complete'
_53
: 'http://localhost:8100/auth-action-complete',
_53
scope: 'openid offline_access email picture profile',
_53
};
_53
let authResult: AuthResult | null = null;
_53
_53
export const useAuthentication = () => ({
_53
initializeAuthentication: async (): Promise<void> =>
_53
AuthConnect.setup({
_53
platform: isNative ? 'capacitor' : 'web',
_53
logLevel: 'DEBUG',
_53
ios: {
_53
webView: 'private',
_53
},
_53
web: {
_53
uiMode: 'popup',
_53
authFlow: 'PKCE',
_53
},
_53
}),
_53
isAuthenticated: async (): Promise<boolean> => {
_53
return (
_53
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_53
);
_53
},
_53
login: async (): Promise<void> => {
_53
authResult = await AuthConnect.login(provider, authOptions);
_53
},
_53
logout: async (): Promise<void> => {
_53
if (authResult) {
_53
await AuthConnect.logout(provider, authResult);
_53
authResult = null;
_53
}
_53
},
_53
});

If we have an AuthResult with an access token we will assume that we are authenticated. The authentication session could be expired or otherwise invalid, but we will work on handling that in other tutorials.

src/composables/authentication.ts
src/views/Tab1Page.vue

_43
<template>
_43
<ion-page>
_43
<ion-header>
_43
<ion-toolbar>
_43
<ion-title>Tab 1</ion-title>
_43
</ion-toolbar>
_43
</ion-header>
_43
<ion-content :fullscreen="true">
_43
<ion-header collapse="condense">
_43
<ion-toolbar>
_43
<ion-title size="large">Tab 1</ion-title>
_43
</ion-toolbar>
_43
</ion-header>
_43
_43
<ion-button @click="loginClicked">Login</ion-button>
_43
<ion-button @click="logoutClicked">Logout</ion-button>
_43
</ion-content>
_43
</ion-page>
_43
</template>
_43
_43
<script setup lang="ts">
_43
import {
_43
IonButton,
_43
IonPage,
_43
IonHeader,
_43
IonToolbar,
_43
IonTitle,
_43
IonContent,
_43
} from '@ionic/vue';
_43
import { useAuthentication } from '@/composables/authentication';
_43
import { ref } from 'vue';
_43
_43
const { login, logout } = useAuthentication();
_43
const authenticated = ref<boolean>();
_43
_43
const loginClicked = async (): Promise<void> => {
_43
await login();
_43
};
_43
_43
const logoutClicked = async (): Promise<void> => {
_43
await logout();
_43
};
_43
</script>

Create an authenticated value for the Tab1Page.

src/composables/authentication.ts
src/views/Tab1Page.vue

_51
<template>
_51
<ion-page>
_51
<ion-header>
_51
<ion-toolbar>
_51
<ion-title>Tab 1</ion-title>
_51
</ion-toolbar>
_51
</ion-header>
_51
<ion-content :fullscreen="true">
_51
<ion-header collapse="condense">
_51
<ion-toolbar>
_51
<ion-title size="large">Tab 1</ion-title>
_51
</ion-toolbar>
_51
</ion-header>
_51
_51
<ion-button @click="loginClicked">Login</ion-button>
_51
<ion-button @click="logoutClicked">Logout</ion-button>
_51
</ion-content>
_51
</ion-page>
_51
</template>
_51
_51
<script setup lang="ts">
_51
import {
_51
IonButton,
_51
IonPage,
_51
IonHeader,
_51
IonToolbar,
_51
IonTitle,
_51
IonContent,
_51
} from '@ionic/vue';
_51
import { useAuthentication } from '@/composables/authentication';
_51
import { ref } from 'vue';
_51
_51
const { isAuthenticated, login, logout } = useAuthentication();
_51
const authenticated = ref<boolean>();
_51
_51
const checkAuthentication = async (): Promise<void> => {
_51
authenticated.value = await isAuthenticated();
_51
};
_51
_51
const loginClicked = async (): Promise<void> => {
_51
await login();
_51
await checkAuthentication();
_51
};
_51
_51
const logoutClicked = async (): Promise<void> => {
_51
await logout();
_51
await checkAuthentication();
_51
};
_51
_51
checkAuthentication();
_51
</script>

Check if we are authenticated when the page is page is created as well as after a login or logout operation.

src/composables/authentication.ts
src/views/Tab1Page.vue

_53
<template>
_53
<ion-page>
_53
<ion-header>
_53
<ion-toolbar>
_53
<ion-title>Tab 1</ion-title>
_53
</ion-toolbar>
_53
</ion-header>
_53
<ion-content :fullscreen="true">
_53
<ion-header collapse="condense">
_53
<ion-toolbar>
_53
<ion-title size="large">Tab 1</ion-title>
_53
</ion-toolbar>
_53
</ion-header>
_53
_53
<ion-button v-if="authenticated" @click="logoutClicked"
_53
>Logout</ion-button
_53
>
_53
<ion-button v-else @click="loginClicked">Login</ion-button>
_53
</ion-content>
_53
</ion-page>
_53
</template>
_53
_53
<script setup lang="ts">
_53
import {
_53
IonButton,
_53
IonPage,
_53
IonHeader,
_53
IonToolbar,
_53
IonTitle,
_53
IonContent,
_53
} from '@ionic/vue';
_53
import { useAuthentication } from '@/composables/authentication';
_53
import { ref } from 'vue';
_53
_53
const { isAuthenticated, login, logout } = useAuthentication();
_53
const authenticated = ref<boolean>();
_53
_53
const checkAuthentication = async (): Promise<void> => {
_53
authenticated.value = await isAuthenticated();
_53
};
_53
_53
const loginClicked = async (): Promise<void> => {
_53
await login();
_53
await checkAuthentication();
_53
};
_53
_53
const logoutClicked = async (): Promise<void> => {
_53
await logout();
_53
await checkAuthentication();
_53
};
_53
_53
checkAuthentication();
_53
</script>

Render the logout button if the user is authenticated. Render the login button if the user is not authenticated.

If we have an AuthResult with an access token we will assume that we are authenticated. The authentication session could be expired or otherwise invalid, but we will work on handling that in other tutorials.

Create an authenticated value for the Tab1Page.

Check if we are authenticated when the page is page is created as well as after a login or logout operation.

Render the logout button if the user is authenticated. Render the login button if the user is not authenticated.

src/composables/authentication.ts
src/views/Tab1Page.vue

_53
import { Capacitor } from '@capacitor/core';
_53
import {
_53
Auth0Provider,
_53
AuthConnect,
_53
AuthResult,
_53
ProviderOptions,
_53
} from '@ionic-enterprise/auth';
_53
_53
const isNative = Capacitor.isNativePlatform();
_53
const provider = new Auth0Provider();
_53
const authOptions: ProviderOptions = {
_53
audience: 'https://io.ionic.demo.ac',
_53
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_53
discoveryUrl:
_53
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_53
logoutUrl: isNative
_53
? 'io.ionic.acdemo://auth-action-complete'
_53
: 'http://localhost:8100/auth-action-complete',
_53
redirectUri: isNative
_53
? 'io.ionic.acdemo://auth-action-complete'
_53
: 'http://localhost:8100/auth-action-complete',
_53
scope: 'openid offline_access email picture profile',
_53
};
_53
let authResult: AuthResult | null = null;
_53
_53
export const useAuthentication = () => ({
_53
initializeAuthentication: async (): Promise<void> =>
_53
AuthConnect.setup({
_53
platform: isNative ? 'capacitor' : 'web',
_53
logLevel: 'DEBUG',
_53
ios: {
_53
webView: 'private',
_53
},
_53
web: {
_53
uiMode: 'popup',
_53
authFlow: 'PKCE',
_53
},
_53
}),
_53
isAuthenticated: async (): Promise<boolean> => {
_53
return (
_53
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_53
);
_53
},
_53
login: async (): Promise<void> => {
_53
authResult = await AuthConnect.login(provider, authOptions);
_53
},
_53
logout: async (): Promise<void> => {
_53
if (authResult) {
_53
await AuthConnect.logout(provider, authResult);
_53
authResult = null;
_53
}
_53
},
_53
});

Which button is shown on the Tab1Page is now determined by the current authentication state.

Persist the AuthResult

The user can perform login and logout operations, but if the browser is refreshed, the application loses the AuthResult. This value needs to be persisted between sessions of the application. To fix this, create a session composable that uses the Preferences plugin to persist the AuthResult. In a production application, we should store the result more securely by using Identity Vault. However, setting up Identity Vault is beyond the scope of this tutorial.

Terminal

_10
npm install @capacitor/preferences

Build out the useSession composable in src/composables/session.ts.

src/composables/session.ts
src/composables/authentication.ts

_10
export const useSession = () => ({});

The useSession functions starts with the basic skeleton.

src/composables/session.ts
src/composables/authentication.ts

_10
import { Preferences } from '@capacitor/preferences';
_10
import { AuthResult } from '@ionic-enterprise/auth';
_10
_10
export const useSession = () => ({});

Import the Preferences and AuthResult classes.

src/composables/session.ts
src/composables/authentication.ts

_17
import { Preferences } from '@capacitor/preferences';
_17
import { AuthResult } from '@ionic-enterprise/auth';
_17
_17
const key = 'session';
_17
_17
export const useSession = () => ({
_17
clearSession: (): Promise<void> => {
_17
return Preferences.remove({ key });
_17
},
_17
getSession: async (): Promise<AuthResult | null> => {
_17
const { value } = await Preferences.get({ key });
_17
return value ? JSON.parse(value) : null;
_17
},
_17
setSession: (value: AuthResult): Promise<void> => {
_17
return Preferences.set({ key, value: JSON.stringify(value) });
_17
},
_17
});

Create functions to get, set, and clear the session.

src/composables/session.ts
src/composables/authentication.ts

_56
import { Capacitor } from '@capacitor/core';
_56
import {
_56
Auth0Provider,
_56
AuthConnect,
_56
AuthResult,
_56
ProviderOptions,
_56
} from '@ionic-enterprise/auth';
_56
import { useSession } from '@/composables/session';
_56
_56
const isNative = Capacitor.isNativePlatform();
_56
const provider = new Auth0Provider();
_56
const authOptions: ProviderOptions = {
_56
audience: 'https://io.ionic.demo.ac',
_56
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_56
discoveryUrl:
_56
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_56
logoutUrl: isNative
_56
? 'io.ionic.acdemo://auth-action-complete'
_56
: 'http://localhost:8100/auth-action-complete',
_56
redirectUri: isNative
_56
? 'io.ionic.acdemo://auth-action-complete'
_56
: 'http://localhost:8100/auth-action-complete',
_56
scope: 'openid offline_access email picture profile',
_56
};
_56
let authResult: AuthResult | null = null;
_56
_56
const { clearSession, getSession, setSession } = useSession();
_56
_56
export const useAuthentication = () => ({
_56
initializeAuthentication: async (): Promise<void> =>
_56
AuthConnect.setup({
_56
platform: isNative ? 'capacitor' : 'web',
_56
logLevel: 'DEBUG',
_56
ios: {
_56
webView: 'private',
_56
},
_56
web: {
_56
uiMode: 'popup',
_56
authFlow: 'PKCE',
_56
},
_56
}),
_56
isAuthenticated: async (): Promise<boolean> => {
_56
return (
_56
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_56
);
_56
},
_56
login: async (): Promise<void> => {
_56
authResult = await AuthConnect.login(provider, authOptions);
_56
},
_56
logout: async (): Promise<void> => {
_56
if (authResult) {
_56
await AuthConnect.logout(provider, authResult);
_56
authResult = null;
_56
}
_56
},
_56
});

Import useSession into our useAuthentication composable and destructure the clearSession, getSession, and setSession functions from it.

src/composables/session.ts
src/composables/authentication.ts

_68
import { Capacitor } from '@capacitor/core';
_68
import {
_68
Auth0Provider,
_68
AuthConnect,
_68
AuthResult,
_68
ProviderOptions,
_68
} from '@ionic-enterprise/auth';
_68
import { useSession } from '@/composables/session';
_68
_68
const isNative = Capacitor.isNativePlatform();
_68
const provider = new Auth0Provider();
_68
const authOptions: ProviderOptions = {
_68
audience: 'https://io.ionic.demo.ac',
_68
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_68
discoveryUrl:
_68
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_68
logoutUrl: isNative
_68
? 'io.ionic.acdemo://auth-action-complete'
_68
: 'http://localhost:8100/auth-action-complete',
_68
redirectUri: isNative
_68
? 'io.ionic.acdemo://auth-action-complete'
_68
: 'http://localhost:8100/auth-action-complete',
_68
scope: 'openid offline_access email picture profile',
_68
};
_68
let authResult: AuthResult | null = null;
_68
_68
const { clearSession, getSession, setSession } = useSession();
_68
_68
const getAuthResult = async (): Promise<AuthResult | null> => {
_68
return getSession();
_68
};
_68
_68
const saveAuthResult = async (authResult: AuthResult | null): Promise<void> => {
_68
if (authResult) {
_68
await setSession(authResult);
_68
} else {
_68
await clearSession();
_68
}
_68
};
_68
_68
export const useAuthentication = () => ({
_68
initializeAuthentication: async (): Promise<void> =>
_68
AuthConnect.setup({
_68
platform: isNative ? 'capacitor' : 'web',
_68
logLevel: 'DEBUG',
_68
ios: {
_68
webView: 'private',
_68
},
_68
web: {
_68
uiMode: 'popup',
_68
authFlow: 'PKCE',
_68
},
_68
}),
_68
isAuthenticated: async (): Promise<boolean> => {
_68
return (
_68
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_68
);
_68
},
_68
login: async (): Promise<void> => {
_68
authResult = await AuthConnect.login(provider, authOptions);
_68
},
_68
logout: async (): Promise<void> => {
_68
if (authResult) {
_68
await AuthConnect.logout(provider, authResult);
_68
authResult = null;
_68
}
_68
},
_68
});

Create functions to get and save the AuthResult.

src/composables/session.ts
src/composables/authentication.ts

_70
import { Capacitor } from '@capacitor/core';
_70
import {
_70
Auth0Provider,
_70
AuthConnect,
_70
AuthResult,
_70
ProviderOptions,
_70
} from '@ionic-enterprise/auth';
_70
import { useSession } from '@/composables/session';
_70
_70
const isNative = Capacitor.isNativePlatform();
_70
const provider = new Auth0Provider();
_70
const authOptions: ProviderOptions = {
_70
audience: 'https://io.ionic.demo.ac',
_70
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_70
discoveryUrl:
_70
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_70
logoutUrl: isNative
_70
? 'io.ionic.acdemo://auth-action-complete'
_70
: 'http://localhost:8100/auth-action-complete',
_70
redirectUri: isNative
_70
? 'io.ionic.acdemo://auth-action-complete'
_70
: 'http://localhost:8100/auth-action-complete',
_70
scope: 'openid offline_access email picture profile',
_70
};
_70
_70
const { clearSession, getSession, setSession } = useSession();
_70
_70
const getAuthResult = async (): Promise<AuthResult | null> => {
_70
return getSession();
_70
};
_70
_70
const saveAuthResult = async (authResult: AuthResult | null): Promise<void> => {
_70
if (authResult) {
_70
await setSession(authResult);
_70
} else {
_70
await clearSession();
_70
}
_70
};
_70
_70
export const useAuthentication = () => ({
_70
initializeAuthentication: async (): Promise<void> =>
_70
AuthConnect.setup({
_70
platform: isNative ? 'capacitor' : 'web',
_70
logLevel: 'DEBUG',
_70
ios: {
_70
webView: 'private',
_70
},
_70
web: {
_70
uiMode: 'popup',
_70
authFlow: 'PKCE',
_70
},
_70
}),
_70
isAuthenticated: async (): Promise<boolean> => {
_70
const authResult = await getAuthResult();
_70
return (
_70
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_70
);
_70
},
_70
login: async (): Promise<void> => {
_70
const authResult = await AuthConnect.login(provider, authOptions);
_70
await saveAuthResult(authResult);
_70
},
_70
logout: async (): Promise<void> => {
_70
const authResult = await getAuthResult();
_70
if (authResult) {
_70
await AuthConnect.logout(provider, authResult);
_70
saveAuthResult(null);
_70
}
_70
},
_70
});

Use the new functions instead of the authResult variable, which can be removed now.

The useSession functions starts with the basic skeleton.

Import the Preferences and AuthResult classes.

Create functions to get, set, and clear the session.

Import useSession into our useAuthentication composable and destructure the clearSession, getSession, and setSession functions from it.

Create functions to get and save the AuthResult.

Use the new functions instead of the authResult variable, which can be removed now.

src/composables/session.ts
src/composables/authentication.ts

_10
export const useSession = () => ({});

If the user logs in and refreshes the browser or restarts the application the authentication state is preserved.

Next Steps

Explore the specific topics that are of interest to you at this time. This application is used as the foundation to build upon as those topics are explored.

Happy coding!! 🤓