Skip to content

LG-14261 Add attempt count to mfa setup auth events#11293

Merged
kevinsmaster5 merged 55 commits intomainfrom
kmas-lg-14261-add-attempt-count-to-mfa-setup-auth
Oct 23, 2024
Merged

LG-14261 Add attempt count to mfa setup auth events#11293
kevinsmaster5 merged 55 commits intomainfrom
kmas-lg-14261-add-attempt-count-to-mfa-setup-auth

Conversation

@kevinsmaster5
Copy link
Contributor

🎫 Ticket

Link to the relevant ticket:
LG-14261

🛠 Summary of changes

Adds a session token to track number of attempts when setting up an mfa type.
Total sum of attempts gets passed to analytics_events.
Backup codes are excluded since during setup they're added instantly.

📜 Testing Plan

Checklist of steps to confirm the changes.

  • In a separate console run make watch_events
  • http://localhost:3000 with existing or new account add MFA.
  • Observe in the watch_events feed 'Multi-Factor Authentication Setup' that a predictable value for
    properties > event_properties > mfa_attempts is registered

Attempt count will increment by failing and retrying to add mfa for example:

  • Webauthn (Face|Touch or security key) clicking cancel at the browser dialog before completing setup
  • PIV test screen select a failing condition
  • SMS/Voice enter (change) supplied auth code
  • Auth App enter wrong auth code

👀 Screenshots

Screenshot 2024-09-26 at 11 39 10 AM (2)

Screenshot 2024-09-26 at 11 36 53 AM (2)

@kevinsmaster5
Copy link
Contributor Author

Taking a look at second_factor_attempts_count to see if that might take the place of creating a session token.

@kevinsmaster5
Copy link
Contributor Author

Taking a look at second_factor_attempts_count to see if that might take the place of creating a session token.

seems to only work for OTP.

@kevinsmaster5 kevinsmaster5 marked this pull request as ready for review September 30, 2024 13:31
@kevinsmaster5 kevinsmaster5 requested a review from a team September 30, 2024 14:53
Copy link
Contributor

@mdiarra3 mdiarra3 left a comment

Choose a reason for hiding this comment

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

Looks good and seems to work locally, but wondering if theres a way for us to push this out to a helper or concern for the verification controllers.

@kevinsmaster5
Copy link
Contributor Author

Looks good and seems to work locally, but wondering if theres a way for us to push this out to a helper or concern for the verification controllers.

Do you mean something like abstracting this sort of thing into a concern?

session[:piv_cac_attempts] ||= 0
session[:piv_cac_attempts] += 1

It does look repetitive since it's sort of identical in 4 places.

@kevinsmaster5 kevinsmaster5 requested a review from mdiarra3 October 3, 2024 16:41
@kevinsmaster5
Copy link
Contributor Author

Added suggestion from #11293 (review)

Copy link
Contributor

@aduth aduth left a comment

Choose a reason for hiding this comment

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

The ticket wants this to be included for both setup and verification attempts, but the pull request is currently focused on setup only.

@aduth
Copy link
Contributor

aduth commented Oct 7, 2024

If someone fails authenticating or setting up with one authentication method and then switches to another, what do we expect the mfa_attempts to be for that second MFA method? I think we wouldn't want to unfairly bias attempts for that second method based on failures with another method.

I think we should either only log the attempts for that individual method, or the log value should be a hash of attempts for each method. For both cases, I'd imagine the session value would be a hash of method-to-attempts.

@kevinsmaster5
Copy link
Contributor Author

kevinsmaster5 commented Oct 7, 2024

I think we should either only log the attempts for that individual method, or the log value should be a hash of attempts for each method. For both cases, I'd imagine the session value would be a hash of method-to-attempts.

So rather than
"event_properties": {
"success": true,
"errors": {},
"multi_factor_auth_method": "piv_cac",
"in_account_creation_flow": true,
"enabled_mfa_methods_count": 1,
"mfa_attempts": 1
},

Would we want to see something like
"event_properties": {
"success": true,
"errors": {},
"multi_factor_auth_method": "piv_cac",
"in_account_creation_flow": true,
"enabled_mfa_methods_count": 1,
"mfa_attempts": {
"piv_cac": 1
}
},

@kevinsmaster5 kevinsmaster5 requested a review from aduth October 7, 2024 20:36
@aduth
Copy link
Contributor

aduth commented Oct 8, 2024

I think we could keep the logged value a single integer if we wanted, but in order to make sure that we're only logging attempts for that method, we'd need to track the attempts in the session as per-method (a hash).

So the session value would look something like the hash in your example, and getting the value from the session is something like user_session[:mfa_attempts][method].

Copy link
Contributor

@aduth aduth left a comment

Choose a reason for hiding this comment

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

I'm wondering if it'd be better to implement the incrementing

@kevinsmaster5 kevinsmaster5 requested a review from aduth October 11, 2024 19:45
@voidlily
Copy link
Contributor

can you rebase this branch to pick up changes to the reviewapp deploy process?

@kevinsmaster5 kevinsmaster5 force-pushed the kmas-lg-14261-add-attempt-count-to-mfa-setup-auth branch from df8b957 to da9afdf Compare October 17, 2024 19:04
Comment on lines 25 to 26
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we log errors.personal_key and error_details.personal_key here? I'm surprised this would come up just now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was in response to an error I was getting. I can take another look for more clarity.

Copy link
Contributor Author

@kevinsmaster5 kevinsmaster5 Oct 18, 2024

Choose a reason for hiding this comment

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

It was in the same spec that was exhibiting the session sensitive key error
./spec/features/legacy_passwords_spec.rb:61

  1. legacy passwords signing in with an incorrect uak personal key digest does not grant access
    Failure/Error:
    raise PiiDetected, <<~ERROR
    track_event received pii key path: #{current_keypath.inspect}
    event: #{event} (#{constant_name})
    full event: #{attributes.inspect}
    allowlisted keypaths: #{pii_like_keypaths.inspect}
    ERROR

    FakeAnalytics::PiiDetected:
    track_event received pii key path: [:errors, :personal_key]
    event: Multi-Factor Authentication ()
    full event: {:success=>false, :errors=>{:personal_key=>["Incorrect personal key"]}, :error_details=>{:personal_key=>{:personal_key_incorrect=>true}}, :new_device=>true, :mfa_attempts=>{"personal_key"=>1}, :multi_factor_auth_method=>"personal-key", :enabled_mfa_methods_count=>1}
    allowlisted keypaths: [[:mfa_attempts, :otp], [:error_details, :personal_key]]

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not able to reproduce that test failure locally in your branch when removing this pii_like_keypaths code. Also, mfa_attempts.otp would never exist in the hash structure since you changed that in an earlier commit.

Comment on lines 196 to 199
Copy link
Contributor

Choose a reason for hiding this comment

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

I think now that we're only tracking attempts for the current method, and because auth_method is a redundant property, it'd be nice if we logged the numeric value of attempts instead.

Suggested change
mfa_attempts: {
attempts: 1,
auth_method: 'sms',
},
attempts: 1,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Revised expected analytics event with e6ab87d

Comment on lines 25 to 26
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not able to reproduce that test failure locally in your branch when removing this pii_like_keypaths code. Also, mfa_attempts.otp would never exist in the hash structure since you changed that in an earlier commit.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is accurately counting attempts. If I sat on the WebAuthn setup page and refreshed the page, it would count that as an attempt, but that's not really an attempt. I think we'd want this inside the result.errors.present? check above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved it there. I don't honestly know why that works. Do we expect errors to be present every time?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need these changes anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's right. Those are removed.

@kevinsmaster5 kevinsmaster5 force-pushed the kmas-lg-14261-add-attempt-count-to-mfa-setup-auth branch from 8f17b01 to cb69a2c Compare October 22, 2024 19:30
user_session[:mfa_attempts] ||= {}
user_session[:mfa_attempts][:attempts] ||= 0
if user_session[:mfa_attempts][:auth_method] != auth_method
if user_session[:mfa_attempts][:auth_method].to_s != auth_method.to_s
Copy link
Contributor Author

Choose a reason for hiding this comment

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

webauthn gets converted to a string when it's added to the session
[2] pry(#Users::WebauthnSetupController)> auth_method
=> :webauthn.1 |
[3] pry(#Users::WebauthnSetupController)> user_session[:mfa_attempts]

it would always set the increment to zero because the 2 values were coming out inequal

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thinking it's because the auth method may or may not be set to "_platform" 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that's a good call-out. The WebAuthn controller handles both Security Key and Face or Touch Unlock, and we probably want to differentiate with webauthn_platform vs. webauthn.

Copy link
Contributor

Choose a reason for hiding this comment

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

This reminds me that we have constants defined for each of the expected auth_method values. Using these might also help with issues normalizing to string, since session value should always be a string.

BACKUP_CODE = 'backup_code'
PERSONAL_KEY = 'personal_key'
PIV_CAC = 'piv_cac'
REMEMBER_DEVICE = 'remember_device'
SMS = 'sms'
TOTP = 'totp'
VOICE = 'voice'
WEBAUTHN = 'webauthn'
WEBAUTHN_PLATFORM = 'webauthn_platform'


def create
if UserSessionContext.confirmation_context?(context)
increment_mfa_selection_attempt_count(:otp)
Copy link
Contributor Author

@kevinsmaster5 kevinsmaster5 Oct 23, 2024

Choose a reason for hiding this comment

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

@aduth should this (and others relative to their constants) be

Suggested change
increment_mfa_selection_attempt_count(:otp)
increment_mfa_selection_attempt_count(TwoFactorAuthenticatable::AuthMethod::PIV_CAC)

for example regarding #11293 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah like that, as appropriate for each method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got that in there. It also made the change here #11293 (comment) no longer needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

I still see the symbol :otp used here. Would we want to switch between TwoFactorAuthenticatable::AuthMethod::SMS or TwoFactorAuthenticatable::AuthMethod::VOICE like we're doing with WebAuthn, to be able to better differentiate what delivery method they're failing with?

end

def confirm
increment_mfa_selection_attempt_count(:webauthn)
Copy link
Contributor

Choose a reason for hiding this comment

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

We'll want to use the constant here as well, and incorporate 'webauthn_platform'.

Maybe we want a helper method to avoid noisy if else ?

def webauthn_auth_method
  if @platform_authenticator
    TwoFactorAuthenticatable::AuthMethod::WEBAUTHN_PLATFORM
  else
    TwoFactorAuthenticatable::AuthMethod::WEBAUTHN
  end
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That works great - also for sms/voice.

Copy link
Contributor

@aduth aduth left a comment

Choose a reason for hiding this comment

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

One minor suggestion, but LGTM otherwise 👍

Comment on lines +46 to +54
if @platform_authenticator
increment_mfa_selection_attempt_count(
TwoFactorAuthenticatable::AuthMethod::WEBAUTHN_PLATFORM,
)
else
increment_mfa_selection_attempt_count(
TwoFactorAuthenticatable::AuthMethod::WEBAUTHN,
)
end
Copy link
Contributor

Choose a reason for hiding this comment

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

We can use your new helper method to simplify

Suggested change
if @platform_authenticator
increment_mfa_selection_attempt_count(
TwoFactorAuthenticatable::AuthMethod::WEBAUTHN_PLATFORM,
)
else
increment_mfa_selection_attempt_count(
TwoFactorAuthenticatable::AuthMethod::WEBAUTHN,
)
end
increment_mfa_selection_attempt_count(webauthn_auth_method)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh right! thanks :)

@kevinsmaster5 kevinsmaster5 merged commit 7837514 into main Oct 23, 2024
@kevinsmaster5 kevinsmaster5 deleted the kmas-lg-14261-add-attempt-count-to-mfa-setup-auth branch October 23, 2024 20:15
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.

4 participants