Interface IUserAuthenticationPasskeys

All Known Subinterfaces:
IUserAuthenticationUI

public interface IUserAuthenticationPasskeys
The passkey part (WebAuthn) of the user authentication interface of the SignIn module, and the recent sign-in every change of a sign-in method requires (CC-LOGIN-L8-PASSKEYS, SignIn L-8; G-8 of the SignIn flow design).

A passkey ceremony has two halves. The server half is here: start... returns the options of the ceremony as the JSON text of WebAuthn's PublicKeyCredentialCreationOptionsJSON or PublicKeyCredentialRequestOptionsJSON, and finish... takes the JSON text of the credential the client made (PublicKeyCredential.toJSON(), RegistrationResponseJSON or AuthenticationResponseJSON). The client half - calling navigator.credentials.create() or get() in a Browser, or the native authenticator API in iiziRun - is the client's, and carries the two texts unchanged.

The challenge of a ceremony is made by the server, kept in its memory only, bound to the client session that asked for it, and valid for one finish... call and for a few minutes. The relying party ID is the application's: the domain the application is reached at, configured per application. Nothing a client sends chooses it.

A passkey is accepted with user verification only (a fingerprint, a face or the device's PIN, checked by the authenticator and signed into its answer), so a sign-in with a passkey is a sign-in with two factors and is asked no second one. It is the proof of a second factor of its session, as a verified code is (CC-LOGIN-M11-PASSKEY-PROOF, IUserAuthenticationSecondFactor.getSecondFactorProof(IClientSessionGyro)): the session changes the second factor of the user and trusts its device.

The passkey of an account recovery (CC-LOGIN-PASSKEY-SECOND-FACTOR). A passkey that is registered by a session whose second factor was a recovery code is no proof of a second factor for 24 hours from its registration. Until then it is a first factor, as a password is: where a second factor is due of its user, finishPasskeyAssertion(IClientSessionGyro, String) signs nobody in and answers SigninResult.SUCCESS_SECOND_FACTOR_REQUIRED, and IUserAuthenticationSecondFactor.verifySecondFactor(IClientSessionGyro, String, String, int) completes the sign-in; a confirmation with it sets the time of the session and is no proof.

Every refusal of a credential is answered with the one result SigninResult.INVALID_PASSKEY; the reason is in the server log.

The policy "require a passkey where available". Where the application has it on and the client has reported a platform authenticator, a password alone signs nobody in: the password paths of IUserAuthenticationUI hold the sign-in back, recording nothing in the session, and answer with a result that is pending - SigninResult.SUCCESS_PASSKEY_REQUIRED for a user who has a passkey (the sign-in is completed by finishPasskeyAssertion(IClientSessionGyro, String)), or SigninResult.SUCCESS_PASSKEY_ENROLMENT_REQUIRED for a user who has none (completed by finishPasskeyRegistration(IClientSessionGyro, String, String)). performInternalLogin returns it as the result of its information, which then has no user; changePassword returns it; performInternalSignup, which has no result to return, throws it in its UserException, after the account is created. The sign-in waits for 5 minutes, one per client session. What the client reports about itself is its own statement: the policy asks for a passkey where the client says one is available, and cannot ask where it says none is.

Author:
Christopher Mindus
  • Field Details

    • RECENT_SIGNIN_MS

      static final long RECENT_SIGNIN_MS
      The age of a sign-in that still counts as recent, in milliseconds: 5 minutes.
      See Also:
  • Method Details

    • isPasskeySignInAvailable

      boolean isPasskeySignInAvailable(IClientSessionGyro clientGyro)
      Checks if passkeys are offered to this client session: the application has them enabled, its relying party ID is known, and the client has reported that it can do WebAuthn (setPasskeyCapabilities(IClientSessionGyro, boolean, boolean, boolean)). The "Sign in with a passkey" button is shown only when this method returns true.
      Parameters:
      clientGyro - The Client Session Gyro.
      Returns:
      true when a passkey ceremony can be started for the client session.
    • isPasskeyUsableOnPage

      default boolean isPasskeyUsableOnPage(IClientSessionGyro clientGyro)
      Checks if a passkey can be used on the page of this client session: passkeys are offered (isPasskeySignInAvailable(IClientSessionGyro)) AND the relying party ID the ceremony would use is valid for the host the page was opened on - equal to it, or a domain suffix of it - which is the check the Browser makes before any authenticator is asked (CC-FIX-SIGNIN-RECOVERY, the third amendment: at http://localhost:8080 of a server whose relying party ID is its external host, Chrome refused every creation with a SecurityError, and the page reported it as the user's cancel). Every offer of a passkey - "Sign in with a passkey", the remembered passkey accounts, "Create a passkey", the passkey as the choice of a second factor, the passkey rows of the security page - is made only when this answers true; whether the application offers passkeys at all, and so whether the start page is the one with the passkey button or the classic one, stays isPasskeySignInAvailable(IClientSessionGyro)'s.
      Parameters:
      clientGyro - The Client Session Gyro.
      Returns:
      true when a passkey ceremony can be started AND can succeed on the page's host.
    • getPasskeyRelyingPartyID

      default String getPasskeyRelyingPartyID(IClientSessionGyro clientGyro)
      Gets the relying party ID the application's passkey ceremonies use, for a message that names it (CC-FIX-SIGNIN-RECOVERY).
      Parameters:
      clientGyro - The Client Session Gyro.
      Returns:
      The relying party ID, null when the application offers no passkeys.
    • getPasskeyChallengeValidity

      default long getPasskeyChallengeValidity(IClientSessionGyro clientGyro, boolean conditional)
      The time a challenge the server makes for this client session is valid, in milliseconds (CC-FIX-PASSKEY-RETRY-CHALLENGE). The arming of a passkey ceremony renews the challenge on the client before this time runs out, so that "Try again" - and any passkey button left waiting - never runs a ceremony against a challenge the server has already expired or forgotten; a stale ceremony would succeed on the device and then be refused by the server, which cost the user a wasted gesture and a second tap. The default is the production value; a development or test server may shorten it.
      Parameters:
      clientGyro - The Client Session Gyro.
      conditional - true for the challenge of the user-name autofill, which waits on the page and is kept longer.
      Returns:
      The validity in milliseconds, always positive.
    • renewPasskeyChallenge

      default boolean renewPasskeyChallenge(IClientSessionGyro clientGyro, boolean create)
      Extends the challenge of the ceremony armed on a passkey button of this client session, so that the button does not run its ceremony against a challenge the server has forgotten (CC-FIX-PASSKEY-RETRY-CHALLENGE). The SignIn module calls this before getPasskeyChallengeValidity(IClientSessionGyro, boolean) runs out, for as long as the button waits for the user. The challenge bytes are unchanged and still single-use.
      Parameters:
      clientGyro - The Client Session Gyro.
      create - true for a registration (navigator.credentials.create()), false for a sign-in or a confirmation (get()).
      Returns:
      true when a challenge of that kind was under way for the client session and was extended; false when there is none (the caller stops renewing).
    • setPasskeyCapabilities

      void setPasskeyCapabilities(IClientSessionGyro clientGyro, boolean webAuthn, boolean platformAuthenticator, boolean conditionalMediation)
      Records what the client reports about itself, as the client bridge measured it. The values are the client's own statement: they decide what is offered and whether the policy "require a passkey where available" applies to this client, never whether a credential is accepted.
      Parameters:
      clientGyro - The Client Session Gyro.
      webAuthn - The client can do WebAuthn (PublicKeyCredential exists in a Browser; the passkey plug-in is present in iiziRun).
      platformAuthenticator - The device has a user-verifying platform authenticator (isUserVerifyingPlatformAuthenticatorAvailable()).
      conditionalMediation - The client can offer passkeys in the autofill of the user name field (isConditionalMediationAvailable()).
    • startPasskeyAssertion

      String startPasskeyAssertion(IClientSessionGyro clientGyro, boolean conditional) throws UserException
      Starts a sign-in (or a confirmation of the signed-in user) with a passkey and returns the options for the client's navigator.credentials.get().

      For a client session that is not signed in, the options name no credential: the authenticator offers the passkeys it holds for the application's relying party ID (discoverable credentials), so that no user name is sent and nothing tells whether an account exists. For a sign-in that waits for a passkey (SigninResult.SUCCESS_PASSKEY_REQUIRED) and for a signed-in session, the options name the passkeys of that user. For a sign-in that waits for its second factor (SigninResult.SUCCESS_SECOND_FACTOR_REQUIRED) of a user who has a passkey that may be it, the options name those passkeys, and the answer goes to IUserAuthenticationSecondFactor.verifySecondFactor(IClientSessionGyro, String, String, int) with the method passkey (CC-LOGIN-PASSKEY-SECOND-FACTOR); the options of the autofill never do.

      Parameters:
      clientGyro - The Client Session Gyro.
      conditional - true when the options are for the autofill of the user name field (conditional mediation): the challenge is then kept longer.
      Returns:
      The JSON text of the PublicKeyCredentialRequestOptionsJSON.
      Throws:
      UserException - With SigninResult.PASSKEYS_NOT_AVAILABLE when the application does not offer passkeys, TOO_MANY_INVALID_LOGINS_RETRY_LATER when the client source is held back, or a server or application result.
    • finishPasskeyAssertion

      IUserAuthenticationInfo finishPasskeyAssertion(IClientSessionGyro clientGyro, String responseJSON) throws UserException
      Finishes a sign-in with a passkey: the credential is verified against the challenge of startPasskeyAssertion(IClientSessionGyro, boolean), the origin, the relying party ID and the stored public key, and the user is signed in and recorded in the session. When the session is signed in as that user already, this is a confirmation: the authentication time of the session is set to now (getAuthenticationTime(IClientSessionGyro)). Either way the session has the proof of a second factor from now on, of the method MFAProof.METHOD_PASSKEY - but for the passkey of an account recovery in its first 24 hours, as the class says.
      Parameters:
      clientGyro - The Client Session Gyro.
      responseJSON - The JSON text of the client's AuthenticationResponseJSON.
      Returns:
      The authenticated user information. When the sign-in waits for its second factor (the passkey of an account recovery in its first 24 hours), the information has the result SigninResult.SUCCESS_SECOND_FACTOR_REQUIRED and no user.
      Throws:
      NullPointerException - If responseJSON is null.
      UserException - With SigninResult.INVALID_PASSKEY for a credential that is refused for any reason, SigninResult.PASSKEY_REQUEST_EXPIRED when there is no challenge to answer (none was started, it was used, or its time ran out), TOO_MANY_INVALID_LOGINS_RETRY_LATER when the client source is held back, or a server or application result.
    • startPasskeyRegistration

      String startPasskeyRegistration(IClientSessionGyro clientGyro) throws UserException
      Starts the registration of a new passkey for the signed-in user, or for the user of a sign-in that waits for one (SigninResult.SUCCESS_PASSKEY_ENROLMENT_REQUIRED), and returns the options for the client's navigator.credentials.create(). The options ask for a discoverable credential with user verification and for no attestation, and name the passkeys the user has already so that an authenticator holding one is not registered twice.
      Parameters:
      clientGyro - The Client Session Gyro.
      Returns:
      The JSON text of the PublicKeyCredentialCreationOptionsJSON.
      Throws:
      UserException - With SigninResult.RECENT_SIGNIN_REQUIRED when the session is not signed in or its sign-in is older than RECENT_SIGNIN_MS (reauthenticate(IClientSessionGyro, String) or a passkey confirmation first), SigninResult.PASSKEYS_NOT_AVAILABLE when the application does not offer passkeys or the user has the most passkeys a user may have, or a server or application result.
    • finishPasskeyRegistration

      PasskeyRegistration finishPasskeyRegistration(IClientSessionGyro clientGyro, String responseJSON, String nickname) throws UserException
      Finishes the registration of a passkey: the credential is verified against the challenge of startPasskeyRegistration(IClientSessionGyro), the origin and the relying party ID, and its public key is stored for the user. A sign-in that waited for the passkey is completed: the user is signed in and recorded in the session.
      Parameters:
      clientGyro - The Client Session Gyro.
      responseJSON - The JSON text of the client's RegistrationResponseJSON.
      nickname - The name the user gives the passkey, null or empty for the default (the date of the registration); cut to 100 characters.
      Returns:
      The registration: the new passkey, and the completed sign-in when there was one.
      Throws:
      NullPointerException - If responseJSON is null.
      UserException - With SigninResult.INVALID_PASSKEY, SigninResult.PASSKEY_REQUEST_EXPIRED, or a server or application result.
    • listPasskeys

      List<PasskeyInfo> listPasskeys(IClientSessionGyro clientGyro)
      Lists the passkeys of the signed-in user, the newest first. No key material is returned.
      Parameters:
      clientGyro - The Client Session Gyro.
      Returns:
      The unmodifiable list, empty when the user has none.
      Throws:
      IllegalStateException - If the session is not signed in.
    • renamePasskey

      SigninResult renamePasskey(IClientSessionGyro clientGyro, String passkeyID, String nickname)
      Renames a passkey of the signed-in user.
      Parameters:
      clientGyro - The Client Session Gyro.
      passkeyID - The passkey, PasskeyInfo.id.
      nickname - The new name, not empty; cut to 100 characters.
      Returns:
      SUCCESS; SigninResult.RECENT_SIGNIN_REQUIRED; SigninResult.INVALID_PASSKEY when the user has no such passkey; or a server or application result.
      Throws:
      NullPointerException - If passkeyID or nickname is null.
      IllegalArgumentException - If nickname is blank.
      IllegalStateException - If the session is not signed in.
    • removePasskey

      SigninResult removePasskey(IClientSessionGyro clientGyro, String passkeyID)
      Removes a passkey of the signed-in user. The authenticator keeps its copy, which the server refuses from then on.
      Parameters:
      clientGyro - The Client Session Gyro.
      passkeyID - The passkey, PasskeyInfo.id.
      Returns:
      SUCCESS; SigninResult.RECENT_SIGNIN_REQUIRED; SigninResult.INVALID_PASSKEY when the user has no such passkey; or a server or application result.
      Throws:
      NullPointerException - If passkeyID is null.
      IllegalStateException - If the session is not signed in.
    • reauthenticate

      SigninResult reauthenticate(IClientSessionGyro clientGyro, String password)
      Confirms the signed-in user with the password ("Confirm it is you"), before a change of a sign-in method. The password is checked as a sign-in checks it: counted and held back per account and per client source. On success the authentication time of the session is set to now. The confirmation with a passkey is startPasskeyAssertion(IClientSessionGyro, boolean) and finishPasskeyAssertion(IClientSessionGyro, String) on the signed-in session.
      Parameters:
      clientGyro - The Client Session Gyro.
      password - The password of the signed-in user.
      Returns:
      SUCCESS, INVALID_USER_OR_PASSWORD, TOO_MANY_INVALID_LOGINS_RETRY_LATER, or a server or application result.
      Throws:
      NullPointerException - If password is null.
      IllegalArgumentException - If password is empty.
      IllegalStateException - If the session is not signed in.
    • getAuthenticationTime

      long getAuthenticationTime(IClientSessionGyro clientGyro)
      Gets the time the user of the session was last authenticated: by a sign-in or by a confirmation.
      Parameters:
      clientGyro - The Client Session Gyro.
      Returns:
      The time in epoch milliseconds, zero when the session is not signed in.
    • isRecentlyAuthenticated

      boolean isRecentlyAuthenticated(IClientSessionGyro clientGyro)
      Checks if the user of the session was authenticated within RECENT_SIGNIN_MS: the condition of every change of a sign-in method.
      Parameters:
      clientGyro - The Client Session Gyro.
      Returns:
      true when the session is signed in and its authentication is recent.