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/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
src/main.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
src/main.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
src/main.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
src/main.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
src/main.ts

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

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

_30
import { provideHttpClient, withInterceptors } from '@angular/common/http';
_30
import { APP_INITIALIZER, enableProdMode } from '@angular/core';
_30
import { bootstrapApplication } from '@angular/platform-browser';
_30
import { provideRouter } from '@angular/router';
_30
import { provideIonicAngular } from '@ionic/angular/standalone';
_30
import { AppComponent } from './app/app.component';
_30
import { routes } from './app/app.routes';
_30
import { AuthenticationService } from './app/core/authentication.service';
_30
import { authInterceptor } from './app/core/interceptors/auth.interceptor';
_30
import { unauthInterceptor } from './app/core/interceptors/unauth.interceptor';
_30
import { environment } from './environments/environment';
_30
_30
if (environment.production) {
_30
enableProdMode();
_30
}
_30
_30
const appInitFactory =
_30
(auth: AuthenticationService): (() => Promise<void>) =>
_30
async () => {
_30
await auth.initialize();
_30
};
_30
_30
bootstrapApplication(AppComponent, {
_30
providers: [
_30
{ provide: APP_INITIALIZER, useFactory: appInitFactory, deps: [AuthenticationService], multi: true },
_30
provideHttpClient(withInterceptors([authInterceptor, unauthInterceptor])),
_30
provideRouter(routes),
_30
provideIonicAngular({}),
_30
],
_30
});

The initialize() method we just added is asynchronous and needs to complete before our application mounts, so update the src/main.ts file to add an APP_INITIALIZER.

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 initialize() method we just added is asynchronous and needs to complete before our application mounts, so update the src/main.ts file to add an APP_INITIALIZER.

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

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

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

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

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

src/app/core/authentication.service.ts

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

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.

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

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

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 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/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
_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
_67
initialize(): Promise<void> {
_67
const isNative = Capacitor.isNativePlatform();
_67
return 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
!!this.authResult && (await AuthConnect.isAccessTokenAvailable(this.authResult))
_67
);
_67
}
_67
_67
async login(): Promise<void> {
_67
this.authResult = await AuthConnect.login(this.provider, this.authOptions);
_67
}
_67
_67
async logout(): Promise<void> {
_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

_19
<ion-header [translucent]="true">
_19
<ion-toolbar>
_19
<ion-title> Tab 1 </ion-title>
_19
</ion-toolbar>
_19
</ion-header>
_19
_19
<ion-content [fullscreen]="true">
_19
<ion-header collapse="condense">
_19
<ion-toolbar>
_19
<ion-title size="large">Tab 1</ion-title>
_19
</ion-toolbar>
_19
</ion-header>
_19
_19
@if (authenticated) {
_19
<ion-button (click)="logout()" data-testid="logout-button">Logout</ion-button>
_19
} @else {
_19
<ion-button (click)="login()" data-testid="login-button">Login</ion-button>
_19
}
_19
</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.

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
_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
_67
initialize(): Promise<void> {
_67
const isNative = Capacitor.isNativePlatform();
_67
return 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
!!this.authResult && (await AuthConnect.isAccessTokenAvailable(this.authResult))
_67
);
_67
}
_67
_67
async login(): Promise<void> {
_67
this.authResult = await AuthConnect.login(this.provider, this.authOptions);
_67
}
_67
_67
async logout(): Promise<void> {
_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

_68
import { Injectable } from '@angular/core';
_68
import {
_68
Auth0Provider,
_68
AuthConnect,
_68
AuthResult,
_68
ProviderOptions,
_68
} from '@ionic-enterprise/auth';
_68
import { Capacitor } from '@capacitor/core';
_68
import { SessionService } from './session.service';
_68
_68
@Injectable({
_68
providedIn: 'root',
_68
})
_68
export class AuthenticationService {
_68
private authOptions: ProviderOptions;
_68
private authResult: AuthResult | null = null;
_68
private provider: Auth0Provider;
_68
_68
constructor(private session: SessionService) {
_68
const isNative = Capacitor.isNativePlatform();
_68
this.provider = new Auth0Provider();
_68
this.authOptions = {
_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
}
_68
_68
initialize(): Promise<void> {
_68
const isNative = Capacitor.isNativePlatform();
_68
return 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
}
_68
_68
async isAuthenticated(): Promise<boolean> {
_68
return (
_68
!!this.authResult && (await AuthConnect.isAccessTokenAvailable(this.authResult))
_68
);
_68
}
_68
_68
async login(): Promise<void> {
_68
this.authResult = await AuthConnect.login(this.provider, this.authOptions);
_68
}
_68
_68
async logout(): Promise<void> {
_68
if (this.authResult) {
_68
await AuthConnect.logout(this.provider, this.authResult);
_68
this.authResult = null;
_68
}
_68
}
_68
}

Inject the SessionService into the AuthenticationService.

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

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

Create methods to get and save the AuthResult.

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

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

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