Skip to content

feat: add Passkey and MyAccount API support#927

Merged
cschetan77 merged 5 commits into
mainfrom
feat/passkey-and-myaccount-api
Jul 3, 2026
Merged

feat: add Passkey and MyAccount API support#927
cschetan77 merged 5 commits into
mainfrom
feat/passkey-and-myaccount-api

Conversation

@cschetan77

@cschetan77 cschetan77 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds support for the Passkey and MyAccount APIs to the auth0-angular SDK, exposing them as Observable-based clients on AuthService — consistent with how the existing mfa client is structured.

Passkey API (AuthService.passkey)

Wraps the passkey client from @auth0/auth0-spa-js. Both methods handle the full WebAuthn challenge-response flow internally:

  • passkey.signup(options) — register a new user with a passkey credential
  • passkey.login(options?) — authenticate an existing user via passkey assertion

Both methods call authState.refresh() on success so that isAuthenticated$ and user$ update automatically, consistent with other authentication flows in the SDK. On failure, errors are surfaced on error$ via authState.setError(), matching the pattern used by getAccessTokenSilently and loginWithCustomTokenExchange.

MyAccount API (AuthService.myAccount)

Wraps the myAccount client from @auth0/auth0-spa-js, providing Observable versions of all seven account management operations:

Method Description
getFactors() List factors and their enrollment status
getAuthenticationMethods(type?) List the user's enrolled authentication methods
getAuthenticationMethod(id) Get a single authentication method by ID
deleteAuthenticationMethod(id) Delete an authentication method
updateAuthenticationMethod(id, data) Update an authentication method (name, preferred phone method)
enrollmentChallenge(options) Start enrollment (step 1 of 2)
enrollmentVerify(options) Complete enrollment by verifying the challenge (step 2 of 2)

Other changes

  • ObservablePasskeyApiClient and ObservableMyAccountApiClient interfaces added to interfaces.ts, following the ObservableMfaApiClient pattern
  • All passkey and MyAccount error classes (PasskeyError, PasskeyRegisterError, PasskeyChallengeError, PasskeyGetTokenError, MyAccountApiError) and types re-exported from the public API surface
  • @auth0/auth0-spa-js minimum version bumped to ^2.21.0 where these APIs were introduced

Test plan

  • 33 new unit tests added to auth.service.spec.ts covering: correct delegation to the underlying SDK, expected return values, authState.refresh() called after passkey authentication, error$ receives passkey errors, and error propagation for all methods
  • All 117 tests in auth.service.spec.ts pass (npm test -- --testPathPattern=auth.service.spec)

Manual Testing

All methods were tested end-to-end against a real Auth0 tenant using an Angular app with the local SDK build.

Passkey API

  • passkey.signup() — registered a new Auth0 user via WebAuthn credential creation ceremony; verified tokens returned and user created in tenant
  • passkey.login() — authenticated an existing passkey user via WebAuthn assertion ceremony; verified tokens returned
  • Reactive state (isAuthenticated$, user$) — verified both Observables emit updated values automatically after passkey signup and login (no manual checkSession() call required — authState.refresh() is called internally by the SDK)

MyAccount API

  • myAccount.getFactors() — verified MFA factors returned for authenticated user
  • myAccount.getAuthenticationMethods() — verified full list of enrolled authentication methods returned
  • myAccount.getAuthenticationMethod(id) — verified single method retrieved by ID
  • myAccount.updateAuthenticationMethod(id, body) — verified preferred_authentication_method update for phone; confirmed expected error returned for unsupported method types
  • myAccount.deleteAuthenticationMethod(id) — verified method deleted and confirmed removal via subsequent getAuthenticationMethods() call
  • myAccount.enrollmentChallenge({ type: 'passkey' }) — verified WebAuthn creation challenge returned with correct RP ID matching custom domain
  • myAccount.enrollmentVerify(...) — completed full passkey enrollment ceremony; verified new passkey method created and returned with correct relying_party_id

Usage

import { inject } from '@angular/core';
import { AuthService } from '@auth0/auth0-angular';

const auth = inject(AuthService);

// Passkey login — isAuthenticated$ and user$ update automatically on success
auth.passkey.login().subscribe(tokens => console.log(tokens.access_token));

// List enrolled authentication methods
auth.myAccount.getAuthenticationMethods().subscribe(methods => console.log(methods));

// Enroll a new TOTP authenticator (two-step flow)
auth.myAccount.enrollmentChallenge({ type: 'totp' }).pipe(
  switchMap(challenge =>
    auth.myAccount.enrollmentVerify({
      type: 'totp',
      location: challenge.location,
      auth_session: challenge.auth_session,
      otp_code: userEnteredOtp,
    })
  )
).subscribe(authMethod => console.log('Enrolled:', authMethod));

Summary by CodeRabbit

  • New Features
    • Added AuthService.passkey Observable API for Passkey/WebAuthn signup and login.
    • Added AuthService.myAccount Observable API for factors, authentication-method CRUD, and enrollment (challenge/verify).
    • Expanded the public exports with new Passkey and MyAccount types and error types.
  • Documentation
    • Updated examples with full Passkeys and MyAccount API sections, including enrollment flows and error handling guidance.
  • Tests
    • Extended the AuthService test suite to cover Passkey and MyAccount operations.
  • Chores
    • Updated the underlying SPA SDK to @auth0/auth0-spa-js@^2.21.0.

@cschetan77 cschetan77 requested a review from a team as a code owner June 24, 2026 08:58
@cschetan77 cschetan77 force-pushed the feat/passkey-and-myaccount-api branch from d1ea74c to ef39e17 Compare July 2, 2026 07:27
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds Passkey (WebAuthn) and MyAccount API support to AuthService via new Observable client interfaces and implementations, exports corresponding types from public-api.ts, bumps the @auth0/auth0-spa-js dependency, and includes tests and documentation.

Changes

Passkey and MyAccount API Implementation

Layer / File(s) Summary
Interface contracts for Passkey and MyAccount clients
projects/auth0-angular/src/lib/interfaces.ts
Imports passkey and MyAccount types from @auth0/auth0-spa-js and defines ObservablePasskeyApiClient and ObservableMyAccountApiClient interfaces.
AuthService passkey and myAccount implementation
projects/auth0-angular/src/lib/auth.service.ts
Adds passkey and myAccount Observable properties on AuthService, wiring passkey signup/login to auth0Client.passkey and myAccount methods to auth0Client.myAccount.
Public API re-exports and dependency bump
projects/auth0-angular/src/public-api.ts, package.json, projects/auth0-angular/package.json
Adds Passkey error types, MyAccountApiError, Passkey/MyAccount type re-exports, and updates the @auth0/auth0-spa-js dependency in both package manifests.
Tests for passkey and myAccount flows
projects/auth0-angular/src/lib/auth.service.spec.ts
Adds passkey and myAccount mocks plus tests covering SDK call arguments, return values, authState updates, and error propagation.
Documentation for Passkeys and MyAccount API
EXAMPLES.md
Updates the table of contents and adds Passkeys and MyAccount API sections with setup, usage, enrollment, and error-handling examples.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Component
  participant AuthService
  participant Auth0Client
  participant AuthState

  Component->>AuthService: passkey.signup(options)
  AuthService->>Auth0Client: auth0Client.passkey.signup(options)
  Auth0Client-->>AuthService: TokenEndpointResponse
  AuthService->>AuthState: refresh()
  AuthService-->>Component: emit TokenEndpointResponse

  Component->>AuthService: myAccount.enrollmentChallenge(options)
  AuthService->>Auth0Client: auth0Client.myAccount.enrollmentChallenge(options)
  Auth0Client-->>AuthService: EnrollmentChallengeResponse
  AuthService-->>Component: emit EnrollmentChallengeResponse
Loading

Related PRs: None identified from the provided data.

Suggested labels: enhancement, documentation

Suggested reviewers: none identified from the provided data.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Passkey and MyAccount API support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/passkey-and-myaccount-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
projects/auth0-angular/src/lib/auth.service.ts (2)

530-545: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Inconsistent error-state handling vs. other token-issuing flows.

passkey.signup/login update authState on success but don't call authState.setError() on failure, unlike getAccessTokenSilently, getAccessTokenWithPopup, and loginWithCustomTokenExchange in this same file, which all call this.authState.setError(error) and this.authState.refresh() in a catchError before rethrowing. Since passkey login/signup is a full authentication flow, callers relying on error$ won't see passkey failures reflected there.

♻️ Suggested alignment with existing error-handling pattern
   readonly passkey: ObservablePasskeyApiClient = {
     signup: (options: PasskeySignupOptions) =>
-      from(
-        this.auth0Client.passkey.signup(options).then((tokenResponse) => {
-          this.authState.refresh();
-          return tokenResponse;
-        })
-      ),
+      of(this.auth0Client).pipe(
+        concatMap((client) => client.passkey.signup(options)),
+        tap(() => this.authState.refresh()),
+        catchError((error) => {
+          this.authState.setError(error);
+          this.authState.refresh();
+          return throwError(error);
+        })
+      ),
     login: (options?: PasskeyLoginOptions) =>
-      from(
-        this.auth0Client.passkey.login(options).then((tokenResponse) => {
-          this.authState.refresh();
-          return tokenResponse;
-        })
-      ),
+      of(this.auth0Client).pipe(
+        concatMap((client) => client.passkey.login(options)),
+        tap(() => this.authState.refresh()),
+        catchError((error) => {
+          this.authState.setError(error);
+          this.authState.refresh();
+          return throwError(error);
+        })
+      ),
   };

Note: loginWithPopup follows the same lax pattern as passkey today (no setError on failure), so this is an existing inconsistency in the file rather than one introduced solely by this PR; flagging since passkey/myAccount is new surface area worth aligning with the newer, more complete pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/auth0-angular/src/lib/auth.service.ts` around lines 530 - 545, The
passkey API methods in auth.service.ts follow the success path but do not mirror
the existing error-state pattern used by getAccessTokenSilently,
getAccessTokenWithPopup, and loginWithCustomTokenExchange. Update the
passkey.signup and passkey.login implementations to catch failures, call
this.authState.setError(error), trigger this.authState.refresh(), and then
rethrow so error$ reflects passkey authentication errors consistently.

530-545: 🩺 Stability & Availability | 🔵 Trivial | ⚖️ Poor tradeoff

Eager side-effect execution breaks Observable laziness contract.

this.auth0Client.passkey.signup(options) (and login) executes immediately when the factory arrow function runs, before any subscription occurs — from() only wraps an already-started promise. Consumers expecting standard Observable semantics (deferred execution until subscribe()) may be surprised if they construct the Observable without immediately subscribing (e.g., pass it through share(), store it, or use it in combineLatest), since the WebAuthn ceremony would already have started.

This mirrors the pre-existing mfa client and several other methods (loginWithRedirect, loginWithPopup) in this file, so it's a known convention rather than a new regression — but handleRedirectCallback, getAccessTokenSilently, and loginWithCustomTokenExchange already use defer/concatMap for true laziness. Consider aligning new APIs with the safer pattern going forward.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/auth0-angular/src/lib/auth.service.ts` around lines 530 - 545, The
passkey methods in auth.service.ts start the WebAuthn promise eagerly, which
breaks Observable laziness. Update the ObservablePasskeyApiClient implementation
for passkey.signup and passkey.login to defer the call until subscription, using
the same lazy pattern already used by
handleRedirectCallback/getAccessTokenSilently/loginWithCustomTokenExchange; keep
authState.refresh() inside the deferred async flow so it runs only after the
underlying promise resolves.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@projects/auth0-angular/src/lib/auth.service.ts`:
- Around line 530-545: The passkey API methods in auth.service.ts follow the
success path but do not mirror the existing error-state pattern used by
getAccessTokenSilently, getAccessTokenWithPopup, and
loginWithCustomTokenExchange. Update the passkey.signup and passkey.login
implementations to catch failures, call this.authState.setError(error), trigger
this.authState.refresh(), and then rethrow so error$ reflects passkey
authentication errors consistently.
- Around line 530-545: The passkey methods in auth.service.ts start the WebAuthn
promise eagerly, which breaks Observable laziness. Update the
ObservablePasskeyApiClient implementation for passkey.signup and passkey.login
to defer the call until subscription, using the same lazy pattern already used
by handleRedirectCallback/getAccessTokenSilently/loginWithCustomTokenExchange;
keep authState.refresh() inside the deferred async flow so it runs only after
the underlying promise resolves.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b5576dca-753e-491c-9ee5-001e0c24b240

📥 Commits

Reviewing files that changed from the base of the PR and between 8fe3e60 and ef39e17.

📒 Files selected for processing (7)
  • EXAMPLES.md
  • package.json
  • projects/auth0-angular/package.json
  • projects/auth0-angular/src/lib/auth.service.spec.ts
  • projects/auth0-angular/src/lib/auth.service.ts
  • projects/auth0-angular/src/lib/interfaces.ts
  • projects/auth0-angular/src/public-api.ts

pmathew92
pmathew92 previously approved these changes Jul 2, 2026

@yogeshchoudhary147 yogeshchoudhary147 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enrollment examples (Passkey, TOTP, Phone, Email, Password) use await inside switchMap callbacks that are not declared async. These will fail to compile if copied.

Fix: add async to each affected callback:

// change this
switchMap((challenge) => {
// to this
switchMap(async (challenge) => {

switchMap handles Promise-returning functions natively so this works fine. Library code itself looks good.

Expose `AuthService.passkey` and `AuthService.myAccount` as Observable-based
API clients, wrapping the corresponding clients from `@auth0/auth0-spa-js`.

- `passkey.signup()` and `passkey.login()` handle the full WebAuthn
  challenge-response flow and call `authState.refresh()` on success so that
  `isAuthenticated$` and `user$` update automatically.
- `myAccount` provides Observable wrappers for all seven MyAccount API
  operations: getFactors, getAuthenticationMethods, getAuthenticationMethod,
  deleteAuthenticationMethod, updateAuthenticationMethod, enrollmentChallenge,
  and enrollmentVerify.
- `ObservablePasskeyApiClient` and `ObservableMyAccountApiClient` interfaces
  added to `interfaces.ts`, consistent with the existing `ObservableMfaApiClient`
  pattern.
- All passkey and MyAccount error classes and types re-exported from the public
  API surface.
- Bump `@auth0/auth0-spa-js` minimum to `^2.21.0` where these APIs were
  introduced.
- 31 new unit tests covering delegation, return values, state side-effects, and
  error propagation for both API clients.
- Refactor passkey.signup/login to use of(client).pipe(concatMap, tap,
  catchError) pattern, aligning with getAccessTokenSilently and
  loginWithCustomTokenExchange: errors now emit on error$ via
  authState.setError(), and the Observable is lazy (deferred until
  subscription)
- Add corresponding unit tests asserting error$ receives passkey errors
- Fix switchMap callbacks in EXAMPLES.md enrollment snippets: add async
  to all callbacks that use await (passkey, TOTP, phone, email, password)
@cschetan77 cschetan77 force-pushed the feat/passkey-and-myaccount-api branch from 8610f89 to c381541 Compare July 3, 2026 08:28

@yogeshchoudhary147 yogeshchoudhary147 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

passkey.login() and passkey.signup() both return a TokenEndpointResponse with the access token in hand, but the implementation calls authState.refresh() instead of authState.setAccessToken(). The pattern in this codebase is:

  • No token returned → refresh() (e.g. loginWithPopup)
  • Token returned → setAccessToken() (e.g. loginWithCustomTokenExchange)

Since passkey returns a token response, it should follow loginWithCustomTokenExchange:

login: (options?: PasskeyLoginOptions) =>
  from(
    this.auth0Client.passkey.login(options).then((tokenResponse) => {
      if (tokenResponse.access_token) {
        this.authState.setAccessToken(tokenResponse.access_token);
      }
      return tokenResponse;
    })
  ),

Both approaches update isAuthenticated$ and user$ correctly, so this is not a functional bug. But using refresh() when the token is right there signals the wrong intent and diverges from the established pattern without reason.

Passkey methods return a TokenEndpointResponse so they should follow
the loginWithCustomTokenExchange pattern: call setAccessToken() with
the returned token rather than calling refresh() directly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
projects/auth0-angular/src/lib/auth.service.ts (1)

531-560: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared pipe logic for passkey signup/login.

signup and login share identical tap/catchError logic except for the underlying client call and options type. Consider extracting a small private helper (e.g., handlePasskeyTokenResponse$) to avoid duplicating the access-token-setting and error-handling logic.

♻️ Suggested refactor
+  private passkeyResult$(
+    source: Observable<TokenEndpointResponse>
+  ): Observable<TokenEndpointResponse> {
+    return source.pipe(
+      tap((tokenResponse) => {
+        if (tokenResponse.access_token) {
+          this.authState.setAccessToken(tokenResponse.access_token);
+        }
+      }),
+      catchError((error) => {
+        this.authState.setError(error);
+        this.authState.refresh();
+        return throwError(error);
+      })
+    );
+  }
+
   readonly passkey: ObservablePasskeyApiClient = {
     signup: (options: PasskeySignupOptions) =>
-      of(this.auth0Client).pipe(
-        concatMap((client) => client.passkey.signup(options)),
-        tap((tokenResponse) => {
-          if (tokenResponse.access_token) {
-            this.authState.setAccessToken(tokenResponse.access_token);
-          }
-        }),
-        catchError((error) => {
-          this.authState.setError(error);
-          this.authState.refresh();
-          return throwError(error);
-        })
-      ),
+      this.passkeyResult$(from(this.auth0Client.passkey.signup(options))),
     login: (options?: PasskeyLoginOptions) =>
-      of(this.auth0Client).pipe(
-        concatMap((client) => client.passkey.login(options)),
-        tap((tokenResponse) => {
-          if (tokenResponse.access_token) {
-            this.authState.setAccessToken(tokenResponse.access_token);
-          }
-        }),
-        catchError((error) => {
-          this.authState.setError(error);
-          this.authState.refresh();
-          return throwError(error);
-        })
-      ),
+      this.passkeyResult$(from(this.auth0Client.passkey.login(options))),
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/auth0-angular/src/lib/auth.service.ts` around lines 531 - 560, The
passkey client methods in auth.service.ts duplicate the same token handling and
error handling in both signup and login. Extract the shared Observable pipeline
logic from the passkey object (for example into a private helper such as
handlePasskeyTokenResponse$) so it accepts the client call and options, then
centralize the tap that sets authState access tokens and the catchError that
sets authState error, refreshes state, and rethrows.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@projects/auth0-angular/src/lib/auth.service.ts`:
- Around line 531-560: The passkey client methods in auth.service.ts duplicate
the same token handling and error handling in both signup and login. Extract the
shared Observable pipeline logic from the passkey object (for example into a
private helper such as handlePasskeyTokenResponse$) so it accepts the client
call and options, then centralize the tap that sets authState access tokens and
the catchError that sets authState error, refreshes state, and rethrows.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd03aea7-3954-4480-8d32-71c3f58617ea

📥 Commits

Reviewing files that changed from the base of the PR and between 8610f89 and 53fb18a.

📒 Files selected for processing (7)
  • EXAMPLES.md
  • package.json
  • projects/auth0-angular/package.json
  • projects/auth0-angular/src/lib/auth.service.spec.ts
  • projects/auth0-angular/src/lib/auth.service.ts
  • projects/auth0-angular/src/lib/interfaces.ts
  • projects/auth0-angular/src/public-api.ts
✅ Files skipped from review due to trivial changes (2)
  • package.json
  • EXAMPLES.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • projects/auth0-angular/src/public-api.ts
  • projects/auth0-angular/package.json
  • projects/auth0-angular/src/lib/auth.service.spec.ts
  • projects/auth0-angular/src/lib/interfaces.ts

@cschetan77 cschetan77 merged commit afbbe3c into main Jul 3, 2026
12 of 13 checks passed
@cschetan77 cschetan77 deleted the feat/passkey-and-myaccount-api branch July 3, 2026 10:27
@cschetan77 cschetan77 mentioned this pull request Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants