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/angular tabs based application, perform some basic configuration, and add the iOS and Android platforms.

Terminal

_10
ionic start getting-started-ac tabs --type=angular-standalone

Use the Ionic CLI to generate the application.

Terminal

_10
ionic start getting-started-ac tabs --type=angular-standalone
_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: 'www',
_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=angular-standalone
_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
"ng": "ng",
_15
"start": "ng serve --port=8100",
_15
"build": "ng build && cap sync",
_15
"watch": "ng build --watch --configuration development",
_15
"test": "ng test",
_15
"lint": "ng lint"
_15
},
_15
...
_15
}

We should do a cap sync with each build and ensure that our application is served on port 8100 when we run the development server. Change the scripts in package.json to do this.

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 and ensure that our application is served on port 8100 when we run the development server. Change the scripts in package.json to do this.

Terminal

_10
ionic start getting-started-ac tabs --type=angular-standalone

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 AuthenticationService

All interaction with Auth Connect will be abstracted into an AuthenticationService. Generate that now.

Terminal

_10
ionic generate service core/authentication

Setup and Initialization

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

src/app/core/authentication.service.ts

_10
import { Injectable } from '@angular/core';
_10
_10
@Injectable({
_10
providedIn: 'root',
_10
})
_10
export class AuthenticationService {
_10
constructor() {}
_10
}

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

src/app/core/authentication.service.ts

_11
import { Injectable } from '@angular/core';
_11
import { Capacitor } from '@capacitor/core';
_11
_11
@Injectable({
_11
providedIn: 'root',
_11
})
_11
export class AuthenticationService {
_11
constructor() {
_11
const isNative = Capacitor.isNativePlatform();
_11
}
_11
}

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

src/app/core/authentication.service.ts

_15
import { Injectable } from '@angular/core';
_15
import { Auth0Provider } from '@ionic-enterprise/auth';
_15
import { Capacitor } from '@capacitor/core';
_15
_15
@Injectable({
_15
providedIn: 'root',
_15
})
_15
export class AuthenticationService {
_15
private provider: Auth0Provider;
_15
_15
constructor() {
_15
const isNative = Capacitor.isNativePlatform();
_15
this.provider = new Auth0Provider();
_15
}
_15
}

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/app/core/authentication.service.ts

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

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/app/core/authentication.service.ts

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

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 service so we can ensure the setup has completed before we execute code in methods we will add later.

We will build this service 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 service so we can ensure the setup has completed before we execute code in methods we will add later.

src/app/core/authentication.service.ts

_10
import { Injectable } from '@angular/core';
_10
_10
@Injectable({
_10
providedIn: 'root',
_10
})
_10
export class AuthenticationService {
_10
constructor() {}
_10
}

Create the auth-action-complete Page

Note that the logoutUrl and redirectUri properties are using the /auth-action-complete route. Generate a page for the route.

terminal

_10
ionic generate page auth-action-complete

This page does not need to do anything. When running on the web, the authentication provider will navigate to this route within the OIDC authentication tab. We can just show a blank page.

src/app/auth-action-complete/auth-action-complete.page.html

_10
<ion-content></ion-content>

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() methods.

src/app/core/authentication.service.ts

_22
import { Injectable } from '@angular/core';
_22
import {
_22
Auth0Provider,
_22
AuthConnect,
_22
AuthResult,
_22
ProviderOptions,
_22
} from '@ionic-enterprise/auth';
_22
import { Capacitor } from '@capacitor/core';
_22
_22
@Injectable({
_22
providedIn: 'root',
_22
})
_22
export class AuthenticationService {
_22
private authOptions: ProviderOptions;
_22
private authResult: AuthResult | null = null;
_22
private provider: Auth0Provider;
_22
private isReady: Promise<void>;
_22
_22
constructor() {
_22
// existing constructor code cut for brevity, do not remove in your code
_22
}
_22
}

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 service for now.

src/app/core/authentication.service.ts

_27
import { Injectable } from '@angular/core';
_27
import {
_27
Auth0Provider,
_27
AuthConnect,
_27
AuthResult,
_27
ProviderOptions,
_27
} from '@ionic-enterprise/auth';
_27
import { Capacitor } from '@capacitor/core';
_27
_27
@Injectable({
_27
providedIn: 'root',
_27
})
_27
export class AuthenticationService {
_27
private authOptions: ProviderOptions;
_27
private authResult: AuthResult | null = null;
_27
private provider: Auth0Provider;
_27
private isReady: Promise<void>;
_27
_27
constructor() {
_27
...
_27
}
_27
_27
async login(): Promise<void> {
_27
await this.isReady;
_27
this.authResult = await AuthConnect.login(this.provider, this.authOptions);
_27
}
_27
}

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/app/core/authentication.service.ts

_35
import { Injectable } from '@angular/core';
_35
import {
_35
Auth0Provider,
_35
AuthConnect,
_35
AuthResult,
_35
ProviderOptions,
_35
} from '@ionic-enterprise/auth';
_35
import { Capacitor } from '@capacitor/core';
_35
_35
@Injectable({
_35
providedIn: 'root',
_35
})
_35
export class AuthenticationService {
_35
private authOptions: ProviderOptions;
_35
private authResult: AuthResult | null = null;
_35
private provider: Auth0Provider;
_35
private isReady: Promise<void>;
_35
_35
constructor() {
_35
...
_35
}
_35
_35
async login(): Promise<void> {
_35
await this.isReady;
_35
this.authResult = await AuthConnect.login(this.provider, this.authOptions);
_35
}
_35
_35
async logout(): Promise<void> {
_35
await this.isReady;
_35
if (this.authResult) {
_35
await AuthConnect.logout(this.provider, this.authResult);
_35
this.authResult = null;
_35
}
_35
}
_35
}

For the logout(), when we call 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 service 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 we call Auth Connect we need to pass the provider as well as the AuthResult we established with the login().

src/app/core/authentication.service.ts

_22
import { Injectable } from '@angular/core';
_22
import {
_22
Auth0Provider,
_22
AuthConnect,
_22
AuthResult,
_22
ProviderOptions,
_22
} from '@ionic-enterprise/auth';
_22
import { Capacitor } from '@capacitor/core';
_22
_22
@Injectable({
_22
providedIn: 'root',
_22
})
_22
export class AuthenticationService {
_22
private authOptions: ProviderOptions;
_22
private authResult: AuthResult | null = null;
_22
private provider: Auth0Provider;
_22
private isReady: Promise<void>;
_22
_22
constructor() {
_22
// existing constructor code cut for brevity, do not remove in your code
_22
}
_22
}

Hook Up the Login and Logout

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

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_14
import { Component } from '@angular/core';
_14
import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
_14
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
_14
_14
@Component({
_14
selector: 'app-tab1',
_14
templateUrl: 'tab1.page.html',
_14
styleUrls: ['tab1.page.scss'],
_14
standalone: true,
_14
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
_14
})
_14
export class Tab1Page {
_14
constructor() {}
_14
}

Currently, the Tab1Page contains the default skeleton code.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_15
import { Component } from '@angular/core';
_15
import { IonButton, IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
_15
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
_15
import { AuthenticationService } from './../core/authentication.service';
_15
_15
@Component({
_15
selector: 'app-tab1',
_15
templateUrl: 'tab1.page.html',
_15
styleUrls: ['tab1.page.scss'],
_15
standalone: true,
_15
imports: [IonButton, IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
_15
})
_15
export class Tab1Page {
_15
constructor(private authentication: AuthenticationService) {}
_15
}

Inject our AuthenticationService and import IonButton which we will use shortly.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_23
import { Component } from '@angular/core';
_23
import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/angular/standalone';
_23
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
_23
import { AuthenticationService } from './../core/authentication.service';
_23
_23
@Component({
_23
selector: 'app-tab1',
_23
templateUrl: 'tab1.page.html',
_23
styleUrls: ['tab1.page.scss'],
_23
standalone: true,
_23
imports: [IonButton, IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
_23
})
_23
export class Tab1Page {
_23
constructor(private authentication: AuthenticationService) {}
_23
_23
async login(): Promise<void> {
_23
await this.authentication.login();
_23
}
_23
_23
async logout(): Promise<void> {
_23
await this.authentication.logout();
_23
}
_23
}

Create login() and logout() methods that we can bind to in our template.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_15
<ion-header [translucent]="true">
_15
<ion-toolbar>
_15
<ion-title> Tab 1 </ion-title>
_15
</ion-toolbar>
_15
</ion-header>
_15
_15
<ion-content [fullscreen]="true">
_15
<ion-header collapse="condense">
_15
<ion-toolbar>
_15
<ion-title size="large">Tab 1</ion-title>
_15
</ion-toolbar>
_15
</ion-header>
_15
_15
<app-explore-container name="Tab 1 page"></app-explore-container>
_15
</ion-content>

The app-explore-container component is no longer needed.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_16
<ion-header [translucent]="true">
_16
<ion-toolbar>
_16
<ion-title> Tab 1 </ion-title>
_16
</ion-toolbar>
_16
</ion-header>
_16
_16
<ion-content [fullscreen]="true">
_16
<ion-header collapse="condense">
_16
<ion-toolbar>
_16
<ion-title size="large">Tab 1</ion-title>
_16
</ion-toolbar>
_16
</ion-header>
_16
_16
<ion-button (click)="login()">Login</ion-button>
_16
<ion-button (click)="logout()">Logout</ion-button>
_16
</ion-content>

Replace it with a couple of buttons.

You can also remove any references to ExploreContainerComponent in tabs1.page.ts.

Currently, the Tab1Page contains the default skeleton code.

Inject our AuthenticationService and import IonButton which we will use shortly.

Create login() and logout() methods that we can bind to in our template.

The app-explore-container component is no longer needed.

Replace it with a couple of buttons.

You can also remove any references to ExploreContainerComponent in tabs1.page.ts.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_14
import { Component } from '@angular/core';
_14
import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
_14
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
_14
_14
@Component({
_14
selector: 'app-tab1',
_14
templateUrl: 'tab1.page.html',
_14
styleUrls: ['tab1.page.scss'],
_14
standalone: true,
_14
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
_14
})
_14
export class Tab1Page {
_14
constructor() {}
_14
}

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/app/core/authentication.service.ts
src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

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

If we have an AuthResult with an access token we 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/app/core/authentication.service.ts
src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_30
import { Component } from '@angular/core';
_30
import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/angular/standalone';
_30
import { AuthenticationService } from './../core/authentication.service';
_30
_30
@Component({
_30
selector: 'app-tab1',
_30
templateUrl: 'tab1.page.html',
_30
styleUrls: ['tab1.page.scss'],
_30
standalone: true,
_30
imports: [IonButton, IonHeader, IonToolbar, IonTitle, IonContent],
_30
})
_30
export class Tab1Page {
_30
authenticated = false;
_30
_30
constructor(private authentication: AuthenticationService) {}
_30
_30
async login(): Promise<void> {
_30
await this.authentication.login();
_30
await this.checkAuthentication();
_30
}
_30
_30
async logout(): Promise<void> {
_30
await this.authentication.logout();
_30
await this.checkAuthentication();
_30
}
_30
_30
private async checkAuthentication(): Promise<void> {
_30
this.authenticated = await this.authentication.isAuthenticated();
_30
}
_30
}

Create an authenticated property in the Tab1Page class. Recheck the status after a login and logout actions complete.

src/app/core/authentication.service.ts
src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_34
import { Component, OnInit } from '@angular/core';
_34
import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/angular/standalone';
_34
import { AuthenticationService } from './../core/authentication.service';
_34
_34
@Component({
_34
selector: 'app-tab1',
_34
templateUrl: 'tab1.page.html',
_34
styleUrls: ['tab1.page.scss'],
_34
standalone: true,
_34
imports: [IonButton, IonHeader, IonToolbar, IonTitle, IonContent],
_34
})
_34
export class Tab1Page implements OnInit {
_34
authenticated = false;
_34
_34
constructor(private authentication: AuthenticationService) {}
_34
_34
async ngOnInit() {
_34
await this.checkAuthentication();
_34
}
_34
_34
async login(): Promise<void> {
_34
await this.authentication.login();
_34
await this.checkAuthentication();
_34
}
_34
_34
async logout(): Promise<void> {
_34
await this.authentication.logout();
_34
await this.checkAuthentication();
_34
}
_34
_34
private async checkAuthentication(): Promise<void> {
_34
this.authenticated = await this.authentication.isAuthenticated();
_34
}
_34
}

To ensure that the value is initialized properly, the page should also check on initialization.

src/app/core/authentication.service.ts
src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_35
import { CommonModule } from '@angular/common';
_35
import { Component, OnInit } from '@angular/core';
_35
import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/angular/standalone';
_35
import { AuthenticationService } from './../core/authentication.service';
_35
_35
@Component({
_35
selector: 'app-tab1',
_35
templateUrl: 'tab1.page.html',
_35
styleUrls: ['tab1.page.scss'],
_35
standalone: true,
_35
imports: [CommonModule, IonButton, IonHeader, IonToolbar, IonTitle, IonContent],
_35
})
_35
export class Tab1Page implements OnInit {
_35
authenticated = false;
_35
_35
constructor(private authentication: AuthenticationService) {}
_35
_35
async ngOnInit() {
_35
await this.checkAuthentication();
_35
}
_35
_35
async login(): Promise<void> {
_35
await this.authentication.login();
_35
await this.checkAuthentication();
_35
}
_35
_35
async logout(): Promise<void> {
_35
await this.authentication.logout();
_35
await this.checkAuthentication();
_35
}
_35
_35
private async checkAuthentication(): Promise<void> {
_35
this.authenticated = await this.authentication.isAuthenticated();
_35
}
_35
}

Import the CommonModule so ngIf can be used in the page's template.

src/app/core/authentication.service.ts
src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_16
<ion-header [translucent]="true">
_16
<ion-toolbar>
_16
<ion-title> Tab 1 </ion-title>
_16
</ion-toolbar>
_16
</ion-header>
_16
_16
<ion-content [fullscreen]="true">
_16
<ion-header collapse="condense">
_16
<ion-toolbar>
_16
<ion-title size="large">Tab 1</ion-title>
_16
</ion-toolbar>
_16
</ion-header>
_16
_16
<ion-button *ngIf="!authenticated" (click)="login()">Login</ion-button>
_16
<ion-button *ngIf="authenticated" (click)="logout()">Logout</ion-button>
_16
</ion-content>

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 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 property in the Tab1Page class. Recheck the status after a login and logout actions complete.

To ensure that the value is initialized properly, the page should also check on initialization.

Import the CommonModule so ngIf can be used in the page's template.

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

src/app/core/authentication.service.ts
src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

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

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 service that uses the Preferences plugin to persist the AuthResult. In a production application, we should store the result securely using Identity Vault. However, setting up Identity Vault is beyond the scope of this tutorial.

Terminal

_10
npm install @capacitor/preferences
_10
ionic generate service core/session

First build out the SessionService.

src/app/core/session.service.ts
src/app/core/authentication.service.ts

_10
import { Injectable } from '@angular/core';
_10
_10
@Injectable({
_10
providedIn: 'root',
_10
})
_10
export class SessionService {
_10
constructor() {}
_10
}

The SessionService starts with the basic service skeleton.

src/app/core/session.service.ts
src/app/core/authentication.service.ts

_10
import { Injectable } from '@angular/core';
_10
import { Preferences } from '@capacitor/preferences';
_10
import { AuthResult } from '@ionic-enterprise/auth';
_10
_10
@Injectable({
_10
providedIn: 'root',
_10
})
_10
export class SessionService {
_10
constructor() {}
_10
}

Import the Preferences and AuthResult classes.

src/app/core/session.service.ts
src/app/core/authentication.service.ts

_23
import { Injectable } from '@angular/core';
_23
import { Preferences } from '@capacitor/preferences';
_23
import { AuthResult } from '@ionic-enterprise/auth';
_23
_23
@Injectable({
_23
providedIn: 'root',
_23
})
_23
export class SessionService {
_23
private key = 'session';
_23
_23
clear(): Promise<void> {
_23
return Preferences.remove({ key: this.key });
_23
}
_23
_23
async getSession(): Promise<AuthResult | null> {
_23
const { value } = await Preferences.get({ key: this.key });
_23
return value ? JSON.parse(value) : null;
_23
}
_23
_23
setSession(value: AuthResult): Promise<void> {
_23
return Preferences.set({ key: this.key, value: JSON.stringify(value) });
_23
}
_23
}

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

src/app/core/session.service.ts
src/app/core/authentication.service.ts

_42
import { Injectable } from '@angular/core';
_42
import {
_42
Auth0Provider,
_42
AuthConnect,
_42
AuthResult,
_42
ProviderOptions,
_42
} from '@ionic-enterprise/auth';
_42
import { Capacitor } from '@capacitor/core';
_42
import { SessionService } from './session.service';
_42
_42
@Injectable({
_42
providedIn: 'root',
_42
})
_42
export class AuthenticationService {
_42
private authOptions: ProviderOptions;
_42
private authResult: AuthResult | null = null;
_42
private provider: Auth0Provider;
_42
private isReady: Promise<void>;
_42
_42
constructor(private session: SessionService) {
_42
// existing constructor code cut for brevity, do not remove in your code
_42
}
_42
_42
async isAuthenticated(): Promise<boolean> {
_42
return (
_42
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_42
);
_42
}
_42
_42
async login(): Promise<void> {
_42
await this.isReady;
_42
this.authResult = await AuthConnect.login(this.provider, this.authOptions);
_42
}
_42
_42
async logout(): Promise<void> {
_42
await this.isReady;
_42
if (this.authResult) {
_42
await AuthConnect.logout(this.provider, this.authResult);
_42
this.authResult = null;
_42
}
_42
}
_42
}

Inject the SessionService into the AuthenticationService.

src/app/core/session.service.ts
src/app/core/authentication.service.ts

_54
import { Injectable } from '@angular/core';
_54
import {
_54
Auth0Provider,
_54
AuthConnect,
_54
AuthResult,
_54
ProviderOptions,
_54
} from '@ionic-enterprise/auth';
_54
import { Capacitor } from '@capacitor/core';
_54
import { SessionService } from './session.service';
_54
_54
@Injectable({
_54
providedIn: 'root',
_54
})
_54
export class AuthenticationService {
_54
private authOptions: ProviderOptions;
_54
private authResult: AuthResult | null = null;
_54
private provider: Auth0Provider;
_54
private isReady: Promise<void>;
_54
_54
constructor(private session: SessionService) {
_54
// existing constructor code cut for brevity, do not remove in your code
_54
}
_54
_54
async isAuthenticated(): Promise<boolean> {
_54
return (
_54
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_54
);
_54
}
_54
_54
async login(): Promise<void> {
_54
await this.isReady;
_54
this.authResult = await AuthConnect.login(this.provider, this.authOptions);
_54
}
_54
_54
async logout(): Promise<void> {
_54
await this.isReady;
_54
if (this.authResult) {
_54
await AuthConnect.logout(this.provider, this.authResult);
_54
this.authResult = null;
_54
}
_54
}
_54
_54
private async getAuthResult(): Promise<AuthResult | null> {
_54
return this.session.getSession();
_54
}
_54
_54
private async saveAuthResult(authResult: AuthResult | null): Promise<void> {
_54
if (authResult) {
_54
await this.session.setSession(authResult);
_54
} else {
_54
await this.session.clear();
_54
}
_54
}
_54
}

Create methods to get and save the AuthResult.

src/app/core/session.service.ts
src/app/core/authentication.service.ts

_56
import { Injectable } from '@angular/core';
_56
import {
_56
Auth0Provider,
_56
AuthConnect,
_56
AuthResult,
_56
ProviderOptions,
_56
} from '@ionic-enterprise/auth';
_56
import { Capacitor } from '@capacitor/core';
_56
import { SessionService } from './session.service';
_56
_56
@Injectable({
_56
providedIn: 'root',
_56
})
_56
export class AuthenticationService {
_56
private authOptions: ProviderOptions;
_56
private provider: Auth0Provider;
_56
private isReady: Promise<void>;
_56
_56
constructor(private session: SessionService) {
_56
// existing constructor code cut for brevity, do not remove in your code
_56
}
_56
_56
async isAuthenticated(): Promise<boolean> {
_56
const authResult = await this.getAuthResult();
_56
return (
_56
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_56
);
_56
}
_56
_56
async login(): Promise<void> {
_56
await this.isReady;
_56
const authResult = await AuthConnect.login(this.provider, this.authOptions);
_56
this.saveAuthResult(authResult);
_56
}
_56
_56
async logout(): Promise<void> {
_56
await this.isReady;
_56
const authResult = await this.getAuthResult();
_56
if (authResult) {
_56
await AuthConnect.logout(this.provider, authResult);
_56
this.saveAuthResult(null);
_56
}
_56
}
_56
_56
private async getAuthResult(): Promise<AuthResult | null> {
_56
return this.session.getSession();
_56
}
_56
_56
private async saveAuthResult(authResult: AuthResult | null): Promise<void> {
_56
if (authResult) {
_56
await this.session.setSession(authResult);
_56
} else {
_56
await this.session.clear();
_56
}
_56
}
_56
}

Use the new methods instead of the authResult class property, which can be removed now.

The SessionService starts with the basic service skeleton.

Import the Preferences and AuthResult classes.

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

Inject the SessionService into the AuthenticationService.

Create methods to get and save the AuthResult.

Use the new methods instead of the authResult class property, which can be removed now.

src/app/core/session.service.ts
src/app/core/authentication.service.ts

_10
import { Injectable } from '@angular/core';
_10
_10
@Injectable({
_10
providedIn: 'root',
_10
})
_10
export class SessionService {
_10
constructor() {}
_10
}

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