Angular SDK
The @passkeybase/angular package provides an Angular service, route guard, and HTTP interceptor for integrating passkey authentication into Angular 17+ applications.
Installation
npm install @passkeybase/angular @passkeybase/jsProvider Setup
Use providePasskeyBase() in your application config. Include authHttpInterceptorFn to automatically attach tokens to outgoing HTTP requests.
// 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
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
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
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.
// 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
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
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
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.
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
- JavaScript SDK -- core API reference
- Node.js SDK -- generate enrollment tokens on the server
- Enrollment Guide -- detailed enrollment flow walkthrough