Skip to main content
Version: 5.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

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

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

src/composables/authentication.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

_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

_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

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

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.

The promise returned by AuthConnect.setup() is stored in our composable so we can ensure the setup has completed before we execute code in functions we will add later.

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.

The promise returned by AuthConnect.setup() is stored in our composable so we can ensure the setup has completed before we execute code in functions we will add later.

src/composables/authentication.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

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

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

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

For the login(), we need to pass both the provider and the options we established earlier. Note that we wait for the setup() call to resolve and that we store the result in our session variable.

src/composables/authentication.ts

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

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. Note that we wait for the setup() call to resolve and that we store the result in our session variable.

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

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

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 get an error like this one:


_10
Manifest merger failed : Attribute data@scheme at AndroidManifest.xml requires a placeholder substitution but no value for <AUTH_URL_SCHEME> is provided.

On iOS, the application runs, but 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 section of the android/app/build.gradle file to include the AUTH_URL_SCHEME:

app/build.gradle

_28
apply plugin: 'com.android.application'
_28
_28
android {
_28
namespace "io.ionic.gettingstartedac"
_28
compileSdkVersion rootProject.ext.compileSdkVersion
_28
defaultConfig {
_28
applicationId "io.ionic.gettingstartedac"
_28
minSdkVersion rootProject.ext.minSdkVersion
_28
targetSdkVersion rootProject.ext.targetSdkVersion
_28
'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

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

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

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

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

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

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

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
let authResult: AuthResult | null = null;
_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
const isReady: Promise<void> = 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
_70
export const useAuthentication = () => ({
_70
isAuthenticated: async (): Promise<boolean> => {
_70
return (
_70
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_70
);
_70
},
_70
login: async (): Promise<void> => {
_70
await isReady;
_70
authResult = await AuthConnect.login(provider, authOptions);
_70
},
_70
logout: async (): Promise<void> => {
_70
await isReady;
_70
if (authResult) {
_70
await AuthConnect.logout(provider, authResult);
_70
authResult = null;
_70
}
_70
},
_70
});

Create functions to get and save the AuthResult.

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

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

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!! 🤓