PasskeyBase

Angular SDK

The @passkeybase/angular package provides an Angular service, route guard, and HTTP interceptor for integrating passkey authentication into Angular 17+ applications.


Installation

Terminal
npm install @passkeybase/angular @passkeybase/js

Provider Setup

Use providePasskeyBase() in your application config. Include authHttpInterceptorFn to automatically attach tokens to outgoing HTTP requests.

app.config.ts
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { providePasskeyBase, authHttpInterceptorFn } from '@passkeybase/angular';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(withInterceptors([authHttpInterceptorFn])),
    providePasskeyBase({
      clientId: 'pk_live_abc123',
      domain: 'myapp.auth.ing',
      redirectUri: window.location.origin + '/callback',
      httpInterceptor: {
        allowedList: ['/api/*'],
      },
    }),
  ],
};

PasskeyBaseService

Auth State

Inject PasskeyBaseService to access authentication state as Observables. Use the async pipe in templates for automatic subscription management.

dashboard.component.ts
// dashboard.component.ts
import { Component, inject } from '@angular/core';
import { PasskeyBaseService } from '@passkeybase/angular';
import { AsyncPipe } from '@angular/common';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    @if (auth.isLoading$ | async) {
      <div>Loading...</div>
    } @else if (auth.isAuthenticated$ | async) {
      <h1>Welcome back</h1>
      <button (click)="auth.logout()">Log out</button>
    } @else {
      <button (click)="auth.loginWithRedirect()">Log in with Passkey</button>
    }
  `,
})
export class DashboardComponent {
  auth = inject(PasskeyBaseService);
}

User Profile

Access the authenticated user's profile through the user$ observable.

profile.component.ts
// profile.component.ts
import { Component, inject } from '@angular/core';
import { PasskeyBaseService } from '@passkeybase/angular';
import { AsyncPipe } from '@angular/common';

@Component({
  selector: 'app-profile',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    @if (auth.user$ | async; as user) {
      <h2>{{ user.displayName }}</h2>
      <p>{{ user.email }}</p>
      <p>User ID: {{ user.sub }}</p>
      <p>Passkeys registered: {{ user.credentials.length }}</p>
    }
  `,
})
export class ProfileComponent {
  auth = inject(PasskeyBaseService);
}

Protected Routes

Use the authGuard functional guard to protect routes. Unauthenticated users are automatically redirected to the login page.

app.routes.ts
// app.routes.ts
import { Routes } from '@angular/router';
import { authGuard } from '@passkeybase/angular';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'callback', component: CallbackComponent },
  {
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [authGuard],
  },
  {
    path: 'settings',
    component: SettingsComponent,
    canActivate: [authGuard],
  },
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [authGuard],
    data: { roles: ['admin'] },
  },
];

HTTP Interceptor

The authHttpInterceptorFn automatically attaches the access token to outgoing requests that match the allowedList patterns. No manual token handling is needed in your services.

HTTP interceptor
// The authHttpInterceptorFn automatically attaches access tokens
// to outgoing HTTP requests that match the allowedList patterns.

// app.config.ts
providePasskeyBase({
  clientId: 'pk_live_abc123',
  domain: 'myapp.auth.ing',
  httpInterceptor: {
    allowedList: [
      '/api/*',
      'https://api.myapp.com/*',
      {
        uri: 'https://external-api.com/*',
        tokenOptions: {
          audience: 'https://external-api.com',
          scopes: ['read:data'],
        },
      },
    ],
  },
})

// data.service.ts — no manual token handling needed
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({ providedIn: 'root' })
export class DataService {
  private http = inject(HttpClient);

  getData() {
    // The interceptor adds the Authorization header automatically
    return this.http.get('/api/data');
  }
}

Login and Logout

Call loginWithRedirect() andlogout() from the service.

nav.component.ts
// nav.component.ts
import { Component, inject } from '@angular/core';
import { PasskeyBaseService } from '@passkeybase/angular';
import { AsyncPipe } from '@angular/common';

@Component({
  selector: 'app-nav',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    <nav>
      <span>My App</span>
      @if (auth.isAuthenticated$ | async) {
        <button (click)="handleLogout()">Sign out</button>
      } @else {
        <button (click)="handleLogin()">Sign in with Passkey</button>
      }
    </nav>
  `,
})
export class NavComponent {
  auth = inject(PasskeyBaseService);

  handleLogin() {
    this.auth.loginWithRedirect({
      scopes: ['openid', 'profile', 'email'],
    });
  }

  handleLogout() {
    this.auth.logout({ returnTo: window.location.origin });
  }
}

Handling the Callback

Create a callback component that calls handleRedirectCallback() inngOnInit.

callback.component.ts
// callback.component.ts
import { Component, inject, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { PasskeyBaseService } from '@passkeybase/angular';

@Component({
  selector: 'app-callback',
  standalone: true,
  template: '<div>Completing login...</div>',
})
export class CallbackComponent implements OnInit {
  private auth = inject(PasskeyBaseService);
  private router = inject(Router);

  async ngOnInit() {
    const result = await this.auth.handleRedirectCallback();
    const returnTo = result.state || '/dashboard';
    this.router.navigateByUrl(returnTo);
  }
}

Passkey Enrollment

Use the enrollPasskey() method on the service to open the enrollment popup. The HTTP interceptor will automatically add the auth header when fetching the enrollment token from your backend.

passkey-settings.component.ts
// passkey-settings.component.ts
import { Component, inject } from '@angular/core';
import { PasskeyBaseService } from '@passkeybase/angular';
import { firstValueFrom } from 'rxjs';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-passkey-settings',
  standalone: true,
  template: `
    <h2>Passkey Management</h2>
    <button (click)="handleEnroll()" [disabled]="isEnrolling">
      {{ isEnrolling ? 'Enrolling...' : 'Add a new passkey' }}
    </button>
    @if (error) {
      <p class="error">{{ error }}</p>
    }
  `,
})
export class PasskeySettingsComponent {
  private auth = inject(PasskeyBaseService);
  private http = inject(HttpClient);

  isEnrolling = false;
  error: string | null = null;

  async handleEnroll() {
    this.isEnrolling = true;
    this.error = null;

    try {
      // Fetch an enrollment token from your backend
      const { enrollmentToken } = await firstValueFrom(
        this.http.post<{ enrollmentToken: string }>('/api/enrollment-token', {})
      );

      // Open the enrollment popup
      await this.auth.enrollPasskey(enrollmentToken);
      alert('Passkey added successfully!');
    } catch (err: any) {
      this.error = err.message;
    } finally {
      this.isEnrolling = false;
    }
  }
}

TypeScript Support

The service, guard, interceptor, and configuration are all fully typed. Angular's strict mode is fully supported.

TypeScript types
import type { User, AuthResult } from '@passkeybase/js';
import { PasskeyBaseService } from '@passkeybase/angular';

// The service exposes typed Observables
// auth.isAuthenticated$  — Observable<boolean>
// auth.isLoading$        — Observable<boolean>
// auth.user$             — Observable<User | undefined>

// Provider config is fully typed
import type { PasskeyBaseConfig } from '@passkeybase/angular';

const config: PasskeyBaseConfig = {
  clientId: 'pk_live_abc123',
  domain: 'myapp.auth.ing',
  redirectUri: '/callback',
  httpInterceptor: {
    allowedList: ['/api/*'],
  },
};

Next steps