DEV Community

Cover image for Building an Angular App with Passwordless Authentication
PassageNick for Passage

Posted on • Originally published at passage.id

Building an Angular App with Passwordless Authentication

We here at Passage are all about creating a passwordless world that makes our online lives a whole lot more secure. Angular developers can join that world and quickly integrate Passage’s solution into their applications. I’ll show you how.

Our solution uses the WebAuthn protocol to leverage the biometrics solutions built into their devices (e.g., FaceID, TouchID, Windows Hello, or Android’s fingerprint technology). We’ll create a simple Angular application that only allows authenticated viewers to see a dashboard. Unauthorized viewers will be out of luck.

If you are highly motivated, you can go through the whole demo with me, or if you want to cut to the quick, you can clone our example app from GitHub and jump to the header entitled “Add Passwordless Authentication with Passage.” Either way, you’ll soon see how easy this is for everyone, both for you developer types and your users.

Note: I’ll be assuming that you are pretty familiar with Angular, the Angular CLI, and how things generally work in Angular. If not, Angular.io is a great place to start.

Setup

To create an Angular application, you can run the following at the command line at a command prompt in the directory where you want your application to go:

ng new passage-app
Enter fullscreen mode Exit fullscreen mode

Note: Create the app with the angular router and with regular
CSS Styling

That will create a nice, basic application for you in a subdirectory called passage-app.

So, to include Passage in your application, you are going to want to install it into your application workspace via npm:

npm i @passageidentity/passage-node
Enter fullscreen mode Exit fullscreen mode

That will install our Passage web components, which will enable us to include them later.

Main App Setup

Before we get really rolling, let’s do a little styling. Open up the project’s main CSS file, styles.css, and add the following code to it:

body {
    font-family: sans-serif;
    background-color: #E5E5E5;
    margin: 0;
}
Enter fullscreen mode Exit fullscreen mode

Next, open up the CSS file for the main component — app.component.css — and add this CSS code to it:

.mainContainer {
    background: white;
    box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
    border-radius: 20px;
    width: 600px;
    min-height: 310px;
    margin: 30px auto;
}
.footer {
    text-align: center;
    font-size: 18px;
}
Enter fullscreen mode Exit fullscreen mode

That will get us started with the styling. We’ll be doing more of that here in a few steps.

The Main Component

Next, we’ll get the main component for our application set up. We’ll insert some HTML, set up the routes, and add some more CSS.

For the app-routing.module.ts file, make it look like this:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from 'src/app/home/home.component';
import { DashboardComponent } from 'src/app/dashboard/dashboard.component';

const routes: Routes = [
    {path: '', component: HomeComponent},
    {path: 'dashboard', component: DashboardComponent}
  ];

  @NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
  })
export class AppRoutingModule { }
Enter fullscreen mode Exit fullscreen mode

This code will set two routes, one for the Home component and one for the Dashboard component.

Add the following to the app.component.css file:

.mainContainer {
    background: white;
    box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
    border-radius: 20px;
    width: 310px;
    min-height: 310px;
    margin: 30px auto;
}
.footer {
    text-align: center;
    font-size: 18px;
}
Enter fullscreen mode Exit fullscreen mode

and for the app.component.html file, replace the default HTML (there’s a bunch of it) with this:

<banner></banner>
<div class="mainContainer">
    <router-outlet></router-outlet>
</div>
<div class="footer">
    Learn more with our <a href="https://docs.passage.id">Documentation</a> and <a href="https://github.com/passageidentity">Github</a>.      
</div>
Enter fullscreen mode Exit fullscreen mode

That code will display the banner on top, a footer at the bottom, and will place the content chosen by the router in the middle.

Some Components

Next up, we’ll create three components. Run these three calls to the Angular CLI at the command line:

ng generate component home
ng generate component dashboard
ng generate component banner
Enter fullscreen mode Exit fullscreen mode

Let’s set all this up now.

The Home Component

In the home.component.html file, add this HTML:

<passage-auth [appId]="appId"></passage-auth>

<passage-auth [appId]="appId"></passage-auth>

Enter fullscreen mode Exit fullscreen mode

In the home.component.ts file, add the following code:

import { Component, OnInit } from '@angular/core';
import { environment } from 'src/environments/environment';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  public appId: string = environment.passageAppId;

  constructor() { }

  ngOnInit(): void {
  }
}
Enter fullscreen mode Exit fullscreen mode

And in the home.component.css file:

.mainContainer {
    display: flex;
  }

  .spacer{
    flex-grow: 1;
  }

  .earlyAccessContainer{
    min-width: 325px;
    width: 35%;
    color: white;
    background-color: #27417E;
    border-top-left-radius: 30px;
    border-bottom-left-radius: 30px;
    display: flex;
    flex-direction: column;
  }

  .title {
    font-weight: 700;
    font-size: 48px;
    margin-top: 65px;
    padding-left: 45px;
    padding-right: 65px;
  }

  .bodyText{
    padding-left: 45px;
    padding-right: 45px;
    padding-bottom: 35px;
    font-size: 24px;
  }

  .authContainer{
    width: 65%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-left: 20px;
    padding-right: 20px;
  }

  /* for shorter 13" dekstop screens */
  @media screen and (max-height: 845px) {
    .title {
      font-size: 40px;
    }
    .bodyText{
      font-size: 20px;
    }
  }

  /* steps to mobile rules begin here */
  @media screen and (max-width: 870px) {

    .mainContainer{
      flex-direction: column;
      width: 100%;
    }

    .earlyAccessContainer{
      min-width: unset;
      width: 100%;
      min-height: 30%;
      border-top-left-radius: 30px;
      border-top-right-radius: 30px;
      border-bottom-left-radius: 0px;
      border-bottom-right-radius: 0px;
    }

    .title {
      font-size: 32px;
      margin-top: 30px;
      padding-left: 30px;
      padding-right: 30px;
    }
    .bodyText{
      font-size: 20px;
      padding-left: 30px;
      padding-right: 30px;
      padding-bottom: 15px;
    }

    .authContainer{
      width: 100%;
      height: 100%;
      flex-grow: 1;
      padding-top: 20px;
      padding-left: 0px;
      padding-right: 0px;
    }
  }

  @media screen and (max-width: 670px) {
    .title {
      font-size: 24px;
    }
    .bodyText{
      font-size: 16px;
    }
  }

  @media screen and (max-width: 460px) {
    .earlyAccessContainer{
      min-height: unset;
    }
  }
Enter fullscreen mode Exit fullscreen mode

The Home component doesn’t do anything special — it just holds a single component. That component is a Passage-specific tag, namely <passage-auth>, which will display the Passage web component and do most of the work of registering and authenticating your users.

That tag also includes a reference to an appId which will uniquely identify your application to our servers and which we will create here in a bit. The home.component.ts file pulls the appID value out of the environment.ts file. We’ll set that part up later as well.

The Banner Component

The Banner component displays a nice header on the top of the page. There’s no functionality in the component, so it’s a bit easier to set up.

Set the banner.component.html file to look like this:

<div class="mainHeader">
    <a href="https://passage.id/"><div class="passageLogo"></div></a>
    <div class="headerText">Passage + Angular Example App</div>
    <div class="spacer"></div>
    <a href="https://passage.id/" class="link">Go to Passage</a>
</div>
Enter fullscreen mode Exit fullscreen mode

and the banner.component.css as follows:

.mainHeader{
    padding: 20px 30px;
    display: flex;
    align-items: center;
    background-color: #282727;
    color: white;
}

.headerText {
    font-size: 24px;
    margin-left: 10px;
}

.passageLogo {
    background-image: url('https://storage.googleapis.com/passage-docs/passage-logo-dark.svg');
    background-repeat: no-repeat;
    width: 60px;
    height: 60px;
    cursor: pointer;
}

.spacer {
    flex-grow: 1;
}

.link {
    margin-left: 20px;
    color: white;
    text-decoration-color: white;
}
Enter fullscreen mode Exit fullscreen mode

You can leave the banner.component.ts file alone.

The Dashboard Component

The Dashboard component is where all the “real” work will get done. It will display differently based on whether or not the user is authenticated. I’ll show you the code, and then I’ll discuss more about what it does after we get everything all set up.

The dashboard.component.ts file should include this code:

import { Component, OnInit, Input } from '@angular/core';
import { AuthService } from 'src/app/auth.service';

@Component({
  selector: 'dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
  title = 'dashboard';

  public username: String | undefined = '';
  public isLoading: Boolean = false;
  public isAuthenticated: Boolean = false;

  constructor(private authService: AuthService){}

  ngOnInit(){
    this.isLoading = true;
    this.authService.isLoggedIn().then((result) => {
      if (result) {
        this.isLoading = this.authService.isLoading;
        this.isAuthenticated = this.authService.isAuthenticated;
        this.username = this.authService.username;
      } else {
        this.isLoading = false;
        this.isAuthenticated = false;
        this.username = '';
      }
    })
}
}
Enter fullscreen mode Exit fullscreen mode

Add this HTML to dashboard.component.html:

<div class="dashboard" *ngIf="!isLoading && isAuthenticated">
    <div class="title">
        Welcome!
    </div>
    <div class="message">
        You successfully signed in with Passage.
        <br/><br/>
        Your username is: <b>\{\{ username \}\}
        </b>
    </div>

</div>
<div class="dashboard" *ngIf="!isLoading && !isAuthenticated">
    <div class="title">
       Unauthorized 
    </div>
    <div class="message">
        You have not logged in and cannot view the dashboard.
        <br/><br/>
        <a href="/" className={styles.link}>Login to continue.</a> 
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

and finally, in dashboard.component.css add this code:

.dashboard{
    padding: 30px 30px 20px;
}
.title {
    font-size: 24px;
    font-weight: 700;
    margin-bottom: 30px;
}
.message {
    overflow-wrap: anywhere;
}
.link {
    color: black;
    text-decoration-color: black;
}
Enter fullscreen mode Exit fullscreen mode

An Authorization Service

At the heart of the application is an Authorization service. It is where your application checks the credentials presented.

Create the Authentication service at the command line:

ng generate service Auth
Enter fullscreen mode Exit fullscreen mode

That creates a file called auth.service.ts. Change the code in that file to the following:

import { Injectable } from '@angular/core';
import { PassageUser, PassageUserInfo } from '@passageidentity/passage-elements/passage-user';

@Injectable({
  providedIn: 'root'
})
export class AuthService { 
  isAuthenticated: boolean = false;
  public username: string | undefined = '';
  public isLoading: boolean = true;
  constructor() {

}

  public async isLoggedIn(): Promise<boolean> {
    this.isLoading = true;
    return await new PassageUser().userInfo().then(userInfo => {
       if (userInfo !== undefined) {
        this.isLoading = false;
        this.isAuthenticated = true;
        this.username = userInfo.email ? userInfo.email : userInfo.phone;
       } else {
        this.isLoading = false;
        this.isAuthenticated = false;
        this.username = '';
       }
       return this.isAuthenticated;

  })
}

}
Enter fullscreen mode Exit fullscreen mode

There. That ought to do it. That’s a lot of code, but building it yourself is more fun, right?

Add Passwordless Authentication with Passage

Okay, that part is finished. Now, we need to tell Passage what you are up to and connect Passage to our application.

The first step is to register with Passage. You can do that on this page.

Note that you’ll use our passwordless solution to register and log in!

Once you are in, click the big plus sign to create a new application. From there,

Name your application whatever you like.

Set the Domain to http://localhost:4200 (this is the default for your Angular application)

Set the Redirect URL to: /dashboard

and then select the “Angular” icon and press the “Create New Application” button.

This should set up your application, and you should see the Passage Console displaying something like this:

The Passage Console for a new application

The Passage Dashboard for your Angular Application

In the “App Info” section on the left, you should have an Application ID (I’ve obfuscated mine in the graphic above). Copy that value, and then open up the environment.ts file in your application and add the following entry to the default values found there:

export const environment = {
  production: false,
  passageAppId: '<Put your AppID here>',
};
Enter fullscreen mode Exit fullscreen mode

What this does is uniquely identify your application to Passage and allow us to work our authentication magic as well as keep stats for you about your application and your users.

Okay, if all is put together correctly, you should be able to go to the command line for your application and enter:

ng serve --open
Enter fullscreen mode Exit fullscreen mode

And you should see something that looks like this:

Your Angular app running and asking you to log in

If everything is working, you should now be able to enter an email address into the login screen, register your device via biometrics, and log in to see the /dashboard page. If you don’t log in and try to navigate to the /dashboard page, you won’t be able to see anything except a message telling you that you aren’t logged in.

So What is Going on Here?

Well, a lot is going on here, but most of it is built into the Passage Web Component element.

The Passage element does all the work of registering and logging in users. You don’t have to do anything past what you’ve already done. The default component is pretty plain-looking by default, but don’t worry — you can customize it to your heart’s content.

Passage authenticates users using the WebAuthn protocol as standardized by the FIDO Alliance. Once it authenticates a user, Passage returns a signed JSON Web Token to identify the user. The whole process is hidden from you, the developer, and your users. All they need to worry about is authenticating with a simple biometrics check.

What Does the Code Do?

As far as what the code is up to, we’ll look at two of the files in the application — auth.service.ts and dashboard.component.ts.

First is the Authentication service.

The AuthService class contains three variables — isAuthenticated, username, and isLoading. isAuthenticated is the key variable here because it is the one that will keep track of whether or not the user is logged in.

The class contains a single function called isLoggedIn() . This function uses the PassageUser class, which is included with the Passage code.

The class is imported via the following import statement:

import { PassageUser, PassageUserInfo } from '@passageidentity/passage-elements/passage-user';
Enter fullscreen mode Exit fullscreen mode

So the code in isLoggedin() merely asks the PassageUser() function for it’s userInfo(). That returns a promise, and if the userInfo is defined, then we know a valid user is present. The variables are set appropriately, and the function returns true because isAuthenticated is true. If the userInfo isn’t found, then the opposite occurs.

This takes us to the Dashboard component. In the dashboard.component.ts file, the ngOnInit() method is called, and the AuthService class is used to set the variables for the class, tracking the status of the user. This is again a promise, so that the information can be properly retrieved and read by the HTML file asynchronously.

The dashboard.component.html file is used to display one of two views, depending on whether the user is authenticated. If the user is authenticated, the username is displayed.

Pretty straightforward, I think.

Just for fun, once you run through the registration and login process a few times with a few different email addresses, you can explore the Console and see the data accumulating for your application.

Finding Out More

There is more to Passage. I already mentioned how you can easily customize the look of the Passage element. In addition, you can collect and store additional information about your users if you like. You can also display a user’s profile with a single HTML element. And of course, we will be continually adding new and useful features.

You can see the <passage-profile> element at work in the 02-Login-With-Profile application that is part of our GitHub Angular example.

Learn More About Passage

So there you have it. If you have any questions at all or need more assistance, or just want to find out more about what we at Passage are up to, please email us at support@passage.id, fill out this form, or join our Discord. We are quite pleased to hear from folks.

Top comments (0)