After a user logs in to your site with a password, you can offer them streamlined passkeys enrollment at any time during their user journey.

Figure 1. Enrollment Experience
This user journey is currently available for mobile web and native apps

Prerequisites

Prior to starting post-login enrollment, be sure to complete the following tasks:

How it works

Complete these three steps to implement passkeys post-login enrollment:

Step 1 - Obtain Your Private Key from the OwnID Console

Step 2 - Build an Endpoint to Generate a Signed Auth Token

Step 3 - Frontend Integration

Step 1 - Obtain Your Private Key

Obtain the OwnID application’s private key from the OwnID Console:

Figure 1. Generating a private key
  1. Select your App from the Apps view.
  2. Go to Integration > Security
  3. On the Signing Key panel, click Generate a new key

Copy the key for use in generating a token in the endpoint as discussed in Step 2 below.

You can generate a new key only ONCE. If you do it again you invalidate the previous one.

Step 2 - Build a Token Generation Endpoint

Build a backend endpoint, for example, /generate-auth-token to be called from your frontend. This endpoint’s primary functions are to:

  • Verify the user’s session and extract the email from it
  • Generate a JWT token
  • Return a JSON response with email and token fields
Sample request handlers for Token Generation Endpoint

Step 3 - Frontend Integration

The frontend integration part, exemplified below with the async initiatePasskeysEnrollment function:

  • Fetches the backend endpoint built in Step 2 to generate an auth token
  • Triggers the enrollment UI by calling the ownid.enrollCredential method
  • Handles errors and success results

It’s recommended to trigger the Passkeys enrollment UI on the next page load after login.

initiatePasskeysEnrollment
/**
 * Initiates the passkeys enrollment process for a logged-in user.
 */
async function initiatePasskeysEnrollment() {
    try {
        // Fetch the authentication token from the backend
        const authTokenResponse = await fetch('/generate-auth-token', {
            method: 'POST',
            credentials: 'include' // Includes cookies in the request
        });

        // Check if the response is successful
        if (!authTokenResponse.ok) {
            throw new Error(`HTTP error! status: ${authTokenResponse.status}`);
        }

        // Parse the JSON response
        const authData = await authTokenResponse.json();

        // Extract the token and loginId (user's email) from the response
        const { token: authToken, email: loginId } = authData;

        // Validate that we received both authToken and loginId
        if (!authToken || !loginId) {
            throw new Error('Invalid response: Missing authToken or loginId');
        }

        // Initiate the OwnID passkeys enrollment
        const result = await ownid.enrollCredential(loginId, authToken);

        if (result.data) {
            console.log('Passkeys enrollment successful');
            // You may want to update the UI here to reflect successful enrollment
        } else if (result.error) {
            console.error('OwnID enrollment failed:', result.message);
            // We don't recommend that you interrupt the user journey with an UI error message.
        } else {
            // Process was canceled or dismissed by the user
            console.log('Passkeys enrollment was canceled by the user');
        }

    } catch (error) {
        console.error('Passkeys enrollment process failed:', error);
    }
}

User Experience Considerations

Successful Passkey Creation

When a user successfully creates a passkey, you can provide immediate feedback to confirm their action.

Recommendations:

  • Update the UI with a non-intrusive notification, such as a toast message.
  • Example message: “Passkey successfully created!”
  • Consider using a success icon (e.g., a checkmark) alongside the message for visual reinforcement.

Implementation example:

if (result.data) {
    console.log('Passkeys enrollment successful');
    showToast('Passkey successfully created!', 'success');
    // Additional UI updates as needed
}

Canceled or Dismissed Enrollment

Respect the users choice to cancel or dismiss the passkeys enrollment process.

Recommendations:

  • Do not show any message when the user cancels or dismisses the enrollment.
  • Allow the user journey on the website to continue uninterrupted.
  • Optionally, log this event for analytics purposes, but keep this invisible to the user.

Implementation example:

if (!result.data && !result.error) {
    console.log('Passkeys enrollment was canceled by the user');
    // Do not show any message to the user
    // Continue with the normal user flow
}

Error Handling

In case of errors during the enrollment process, prioritize a smooth user experience.

Recommendations:

  • Do not display error messages to the user for backend errors or OwnID function errors.
  • Continue the user journey as if the enrollment was not attempted.
  • Log errors server-side for debugging and monitoring purposes.

Security Considerations

  1. HTTPS: Ensure all communications between frontend and backend are over HTTPS to protect sensitive data.

  2. Token Expiration: Set a short expiration time for the JWT token (e.g., 1 minute) to minimize the window of potential misuse.

  3. Input Validation: Validate and sanitize all inputs on the backend, even when coming from authenticated sessions.

  4. Private Key Management: Securely manage the private key used for JWT signing. Never expose this key in client-side code or public repositories.

  5. Rate Limiting: Consider implementing rate limiting on the token generation endpoint to prevent abuse.

Next Steps

Ready to deploy?