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
- Select your App from the Apps view.
- Go to Integration > Security
- 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
const express = require('express');
const jwt = require('jsonwebtoken');
const fs = require('fs');
const session = require('express-session');
const app = express();
const port = 3000;
app.use(express.json());
// Read private key
const privateKey = fs.readFileSync('./private.key');
function getUserFromSession(req) {
return req.session.userEmail || null;
}
function generateJWT(userEmail) {
const payload = {
sub: userEmail,
iss: 'OwnID',
aud: 'OwnID',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 60 // 1 minute expiration
};
return jwt.sign(payload, privateKey, { algorithm: 'RS256' });
}
app.post('/generate-auth-token', (req, res) => {
const userEmail = getUserFromSession(req);
if (!userEmail) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
const token = generateJWT(userEmail);
res.json({
email: userEmail,
token: token
});
} catch (error) {
console.error('JWT Generation Error:', error);
res.status(500).json({ error: 'Failed to generate token' });
}
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
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
-
HTTPS: Ensure all communications between frontend and backend are over HTTPS to protect sensitive data.
-
Token Expiration: Set a short expiration time for the JWT token (e.g., 1 minute) to minimize the window of potential misuse.
-
Input Validation: Validate and sanitize all inputs on the backend, even when coming from authenticated sessions.
-
Private Key Management: Securely manage the private key used for JWT signing. Never expose this key in client-side code or public repositories.
-
Rate Limiting: Consider implementing rate limiting on the token generation endpoint to prevent abuse.
Next Steps
Ready to deploy?
Responses are generated using AI and may contain mistakes.