Passkey

WebAuthn

WebAuthn is a web standard for secure and passwordless authentication. It allows users to log in to online services using public key cryptography, eliminating the need for complex passwords and enhancing security against phishing and brute force attacks. WebAuthn is supported by major web browsers and platforms, making it a versatile and user-friendly authentication mechanism. It is a core component of the FIDO (Fast Identity Online) Alliance’s efforts to improve online authentication and security, and it defines passkey as a key type for authentication.

What is a passkey?

Passkey is a passwordless authentication mechanism that leverages public key cryptography to allow users to log in securely without needing to remember complex passwords. It is user-friendly, more secure against phishing attacks, and can be used across different devices and platforms, enhancing the user experience in accessing online services securely.

  • Utilizes public key cryptography for secure authentication.
  • Does not require remembering complex passwords.
  • Resilient against phishing and brute force attacks.
  • Supports seamless cross-device and cross-platform use.
  • Can be used with biometrics, mobile devices, or security keys.
  • Simplifies the login process for users across various services.

How does passkey work?

Passkey leverages public key cryptography to authenticate users securely without requiring complex passwords. It consist of a public and a private key, managed by a user-controlled authenticator. This authenticator verifies the user’s presence and authorization through mechanisms like PINs, biometrics, or device passwords, tailored to its capabilities. Various devices can serve as authenticators, including smartphones, FIDO2 security keys (Yubikeys), or password managers.

In order to establish trust between the user and the service, the authenticator generates a public-private key pair and registers the public key with the service. When the user logs in, the service sends a challenge to the authenticator, which signs it with the private key and sends the signature back to the service. The service verifies the signature using the registered public key, and if the signature is valid, the user is authenticated.

  1. Setting up a WebAuthn library in your Go application to handle the cryptographic challenge-response mechanism.
  2. Implementing endpoint(s) for registration (to create passkeys) and authentication (to verify passkeys) processes.
  3. During registration, your service sends a challenge to the user’s authenticator (device); the user’s device responds with a new public key (the passkey), which your service stores.
  4. For authentication, your service sends a challenge 1 to be signed by the user’s private key; the response is verified using the stored public key.

Amongst the many security features of passkey, it is bound to a website domain, making it difficult for attackers to impersonate the authenticator and steal the user’s credentials. For example, a passkey registered with a online shopping website, my-store.com cannot be used to authenticate with a malicious website, enhancing security against phishing and typosquatting attacks.

Pros and Cons of Passkey

Pros Cons
Enhanced Security: Utilizes public key cryptography, reducing vulnerability to phishing and brute force attacks. Compatibility: Might not be supported by all platforms or services yet, limiting universal adoption.
Convenience: Eliminates the need to remember complex passwords, offering a seamless login experience. Recovery: If the device storing the passkey is lost, recovering access might be challenging without proper backup mechanisms.
Cross-Platform: Can be used across different devices and platforms, facilitating easy access to services. Initial Setup: Requires an initial setup process that might be unfamiliar to some users, potentially hindering immediate adoption.

Code Example

Here are some examples provided by the WebAuthn Go library to demonstrate how to use passkey authentication in a Go application:

Registration of an account

sequenceDiagram
    participant User
    participant Browser
    participant Server
    participant Authenticator

    User->>Browser: Initiates registration
    Browser->>Server: Requests registration
    Server->>Browser: Provides challenge and user info
    Browser->>Authenticator: Requests new credential (challenge, user info)
    Authenticator->>Browser: Returns new credential (public key, credential ID, attestation)
    Browser->>Server: Submits new credential
    Server->>Server: Validates attestation, stores public key and credential ID
    Server->>Browser: Confirms registration success
    Browser->>User: Indicates registration success
package example

func BeginRegistration(w http.ResponseWriter, r *http.Request) {
    user := datastore.GetUser() // Find or create the new user  
    options, session, err := webAuthn.BeginRegistration(user)
    // handle errors if present
    // store the sessionData values 
    JSONResponse(w, options, http.StatusOK) // return the options generated
    // options.publicKey contain our registration options
}

func FinishRegistration(w http.ResponseWriter, r *http.Request) {
    user := datastore.GetUser() // Get the user

    // Get the session data stored from the function above
    session := datastore.GetSession()

    credential, err := webAuthn.FinishRegistration(user, session, r)
    if err != nil {
        // Handle Error and return.

        return
    }

    // If creation was successful, store the credential object
    // Pseudocode to add the user credential.
    user.AddCredential(credential)
    datastore.SaveUser(user)

    JSONResponse(w, "Registration Success", http.StatusOK) // Handle next steps
}

logging into an account

sequenceDiagram
    participant User
    participant Browser
    participant Server
    participant Authenticator

    User->>Browser: Initiates login
    Browser->>Server: Requests login
    Server->>Browser: Provides challenge
    Browser->>Authenticator: Requests signing (challenge)
    Authenticator->>User: User authorizes (e.g., PIN, biometrics)
    User->>Authenticator: Authorizes
    Authenticator->>Browser: Returns signed challenge
    Browser->>Server: Submits signed challenge
    Server->>Server: Validates signature using stored public key
    Server->>Browser: Confirms login success
    Browser->>User: Indicates login success

Example of the clientData

  • challenge: The server must validate that this returned challenge matches the one generated for this registration event.
  • origin: The server must validate that this origin string matches up with the origin of the application.
  • type: The server validates that this string is in fact webauthn.create. If another string is provided, it indicates that the authenticator performed an incorrect operation.
{
    "challenge": "p5aV2uHXr0AOqUk7HQitvi-Ny1....",
    "origin": "https://webauthn.guide",
    "type": "webauthn.create"
}
package example

func BeginLogin(w http.ResponseWriter, r *http.Request) {
    user := datastore.GetUser() // Find the user

    options, session, err := webAuthn.BeginLogin(user)
    if err != nil {
        // Handle Error and return.

        return
    }

    // store the session values
    datastore.SaveSession(session)

    JSONResponse(w, options, http.StatusOK) // return the options generated
    // options.publicKey contain our registration options
}

func FinishLogin(w http.ResponseWriter, r *http.Request) {
    user := datastore.GetUser() // Get the user 

    // Get the session data stored from the function above
    session := datastore.GetSession()

    credential, err := webAuthn.FinishLogin(user, session, r)
    if err != nil {
        // Handle Error and return.

        return
    }

    // Handle credential.Authenticator.CloneWarning

    // If login was successful, update the credential object
    // Pseudocode to update the user credential.
    user.UpdateCredential(credential)
    datastore.SaveUser(user)

    JSONResponse(w, "Login Success", http.StatusOK)
}

  1. In the context of WebAuthn and passkey authentication, a “challenge” is a randomly generated string that the server sends to the client during the authentication process. The client’s device uses its private key to sign this challenge as part of the cryptographic operation. This signed challenge is then sent back to the server, which verifies the signature using the corresponding public key stored during registration. This process ensures that the authentication request is current (to prevent replay attacks) and that the client is in possession of the private key, thus authenticating the user’s identity securely. ↩︎