Skip to content

fix(metrics): correct login.complete for passkey sign-ins - #20970

Draft
vpomerleau wants to merge 1 commit into
mainfrom
FXA-14302
Draft

fix(metrics): correct login.complete for passkey sign-ins#20970
vpomerleau wants to merge 1 commit into
mainfrom
FXA-14302

Conversation

@vpomerleau

Copy link
Copy Markdown
Contributor

Because

  • Every Sync passkey sign-in emitted two login.complete events, so passkey
    adoption and overall login volume have both been over-counted by roughly
    1,700 flows a week since Phase 1 GA.
  • The surviving event was labelled reason=email, so Sync passkey logins were
    attributed to email sign-in and passkey conversion could not be measured from
    the backend funnel.
  • The password step that completes a keys-required passkey sign-in carried no
    flow_id and no relying party, leaving a hole in the flow between the ceremony
    and the login it completes.

This pull request

  • Adds an authMethod argument to setFlowCompleteSignal in
    packages/fxa-auth-server/lib/metrics/context.js, carried on the metrics
    context and copied through gather() so the flow-complete emitter can read it.
  • Reads that value in packages/fxa-auth-server/lib/metrics/events.js, falling
    back to email, making it the single source of the login.complete reason.
  • Removes the unconditional login.complete from the passkey ceremony in
    packages/fxa-auth-server/lib/routes/passkeys.ts, and sets the flow-complete
    signal before emitting account.login so the keys-optional path still records
    a completion.
  • Derives the method from the session token in
    packages/fxa-auth-server/lib/routes/utils/signin.js, so the password step of
    a passkey sign-in reports passkey rather than email.
  • Accepts metricsContext on POST /password/create, forwarding it from
    packages/fxa-auth-client/lib/client.ts and
    packages/fxa-settings/src/pages/PostVerify/SetPassword/container.tsx.
  • Stops stash() overwriting a client-supplied metricsContext.service when the
    request carries none, so login.complete keeps its relying party.
  • Asserts the emission count, not just the reason, in the route specs.

Issue that this pull request solves

Closes: FXA-14302

Checklist

Put an x in the boxes that apply

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).
  • I have manually reviewed all AI generated code.

How to review (Optional)

  • Key files/areas to focus on: lib/metrics/context.js, lib/metrics/events.js,
    lib/routes/passkeys.ts, lib/routes/utils/signin.js
  • Suggested review order: context.jsevents.jspasskeys.ts
    signin.js → specs
  • Risky or complex parts: the statement reorder in passkeys.ts is load-bearing —
    the signal must be set before emitMetricsEvent or the keys-optional path
    emits nothing. The stash() fallback applies to any route that stashes, not
    only passkey ones.

Screenshots (Optional)

Please attach the screenshots of the changes made in case of change in user interface.

Other information (Optional)

  • Pre-merge review waived: metrics-only change, no auth logic altered — the
    password.ts edit is a payload-schema addition and signin.js only derives a
    label.
  • Merging drops reason='email' volume by the duplicate count and shifts
    reason='passkey' later in time. Needs a heads-up to data/PM and a dashboard
    breakpoint date.

This comment was marked as outdated.

Because:
 - A keys-required passkey sign-in emitted two login.complete events under one
   flow_id: reason=passkey at the ceremony, then reason=email at the password
   step, double-counting every Sync passkey login
 - The !keysRequired deferral in sendPostSigninNotifications was correct, but the
   Glean call sat outside it and fired unconditionally
 - The generic flow-complete emitter hardcoded reason=email, with no way to know
   how the user had authenticated
 - The password step carried no metrics context of its own, so it fell outside
   the flow and login.complete lost its relying party

This commit:
 - Adds an authMethod argument to setFlowCompleteSignal, carried on the metrics
   context alongside flowCompleteSignal and flowType and copied through gather()
   so the emitter can read it; kept off the payload schema so a client cannot
   supply one
 - Reads it in the flow-complete emitter, defaulting to email
 - Removes the unconditional login.complete from the passkey ceremony;
   passkey.authentication_verification_success already marks that step
 - Sets the flow-complete signal before emitting account.login on the
   keys-optional path, so the machinery recognises the flow as complete
 - Derives the method from the session token at /session/reauth, so the password
   step of a passkey sign-in reports passkey rather than email
 - Stashes the metrics context against the new passkey session token, so the
   password-creation and key-fetch steps stay on the same flow
 - Stops stash() overwriting a client-supplied service when the request carries
   none, so login.complete keeps its relying party
 - Asserts the emission count, not just the reason, in the route specs

Closes #FXA-14302

Copilot AI 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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/fxa-auth-server/lib/metrics/events.js:146

  • The PR description says this makes metrics/events.js the single source of truth for login.complete reason, but other routes still emit glean.login.complete directly (e.g. lib/routes/passwordless.ts:319 and lib/routes/linked-accounts.ts:598). That means login.complete can still be emitted from multiple places, and those direct emitters can diverge from (or duplicate) the flow-complete reason logic here.
        if (metricsContext.flowType === 'login') {
          glean.login.complete(request, {
            uid: data?.uid ?? '',
            reason: metricsContext.authMethod || 'email',
          });

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.

2 participants