Whatever message this page gives is out now! Go check it out!
| Audience | Use case |
|---|---|
| Development teams | Add passwordless authentication for new or existing ColdFusion apps. |
| Retail and customer-facing apps | Simplified sign-in for users without corporate SSO. |
| Enterprise apps | Offer passkeys as an alternative or replacement for passwords. |
| Compliance teams | Meet security requirements (for example, phishing-resistant MFA). |
| End users | Sign in with biometrics or PIN instead of passwords. |
| Model | Description |
|---|---|
| Discoverable credentials | All passkeys for the site are available. Omit username when calling PasskeyAuthenticate. The user chooses which account to sign in with. |
| User ID driven credentials | Passkeys for a specific user are only available. Pass username when calling PasskeyAuthenticate to restrict the credential lookup. |
rpId (Relying Party ID) binds the credential to your domain.PasskeyRegister(userStruct, configStruct) with username and config.CFPasskey.startRegistration() in JavaScript to initiate the registration workflow.redirectUrl with ?passkey_token=xxx if redirectUrl is specified.PasskeyGetResult(token) and handles success.PasskeyAuthenticate(userStruct, configStruct).CFPasskey.startAuthentication() in JavaScript.redirectUrl with ?passkey_token=xxx.PasskeyGetResult(token) and logs the user in (for example, via <cflogin>).username in userStruct when calling PasskeyAuthenticate.username in userStruct when calling PasskeyAuthenticate.result.action == "registration".<cfscript>
userStruct = { name: "user@example.com" };
configStruct = {
successHandler: "onRegisterSuccess",
errorHandler: "onRegisterError",
redirectUrl: "/callback.cfm",
service: "/CFIDE/passkey/DefaultPasskey.cfc"
};
PasskeyRegister(userStruct, configStruct);
</cfscript>
<script>
function onRegisterSuccess(data, serverResult) { /* handle success */ }
function onRegisterError(errorData) { /* handle error */ }
function startRegistrationWhenReady() {
if (typeof CFPasskey !== 'undefined'
&& typeof CFPasskey.startRegistration === 'function') {
CFPasskey.startRegistration();
} else { setTimeout(startRegistrationWhenReady, 200); }
}
document.addEventListener('DOMContentLoaded', function() {
setTimeout(startRegistrationWhenReady, 200);
});
</script><cfparam name="url.passkey_token" default="">
<cfif len(trim(url.passkey_token))>
<cfset result = PasskeyGetResult(url.passkey_token)>
<cfif result.success>
<cfif result.action == "registration">
<!--- Registration complete.
result.username, result.credentialId available.
Typical next step: redirect to the sign-in page. --->
<cfelseif result.action == "authentication">
<!--- Authentication complete. Establish a session here
(see Integration with cflogin below). --->
</cfif>
</cfif>
</cfif>result.action before acting on the result. The same callback URL receives both registration and authentication tokens, and treating one as the other (for example, calling <cfloginuser> on a registration result) creates a subtle account-takeover risk.<cfscript>
userStruct = { rpId: cgi.server_name };
configStruct = {
successHandler: "onAuthSuccess",
errorHandler: "onAuthError",
redirectUrl: "/callback.cfm",
service: "/CFIDE/passkey/DefaultPasskey.cfc"
};
PasskeyAuthenticate(userStruct, configStruct);
</cfscript>
<script>
function onAuthSuccess(assertionData, serverResult) { /* handle success */ }
function onAuthError(errorData) { /* handle error */ }
function startAuthWhenReady() {
if (typeof CFPasskey !== 'undefined'
&& typeof CFPasskey.startAuthentication === 'function') {
CFPasskey.startAuthentication();
} else { setTimeout(startAuthWhenReady, 200); }
}
document.addEventListener('DOMContentLoaded', function() {
setTimeout(startAuthWhenReady, 200);
});
</script>userStruct parameters| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Username for registration (for example, email). |
displayName | string | No | Human-readable display name. |
id | string | No | Unique user identifier (auto-generated if omitted). |
rpName | string | No | Relying Party name (your app name). |
rpId | string | No | Relying Party ID (defaults to cgi.server_name). |
userVerification | string | No | "required", "preferred", "discouraged". |
authenticatorAttachment | string | No | "platform" (Touch ID, Windows Hello) or "cross-platform" (USB key). |
requireResidentKey | boolean | No | Whether to require resident key. |
attestation | string | No | "none", "indirect", "direct", "enterprise". |
configStruct parameters| Parameter | Type | Required | Description |
|---|---|---|---|
successHandler | string | No | JavaScript function name for success callback. |
errorHandler | string | No | JavaScript function name for error callback. |
redirectUrl | string | No | Path with leading / (for example, /callback.cfm) or full http(s) URL. |
service | string | No | CFC path for credential storage (default: CFIDE/passkey/DefaultPasskey.cfc). |
userStruct parameters| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | No | Username hint for credential lookup (omit for discoverable credentials). |
rpId | string | No | Relying Party ID (defaults to cgi.server_name). |
userVerification | string | No | "required", "preferred", "discouraged". |
configStruct parameters| Parameter | Type | Required | Description |
|---|---|---|---|
successHandler | string | No | JavaScript function name for success callback. |
errorHandler | string | No | JavaScript function name for error callback. |
redirectUrl | string | No | Path with leading / (for example, /callback.cfm) or full http(s) URL. |
service | string | No | CFC path for credential storage (default: CFIDE/passkey/DefaultPasskey.cfc). |
CFIDE/passkey/DefaultPasskey.cfc writes credentials to JSON files on the local server and is intended for development and single-server use only. Production and clustered deployments should override service to /CFIDE/passkey/DatabasePasskey.cfc. The Quick Start examples use the file-based default purely to keep the snippets self-contained. Do not copy them straight into production without changing the service path.?passkey_token=xxx.| Key | Type | Description |
|---|---|---|
success | boolean | Whether the operation succeeded. |
action | string | "registration" or "authentication". |
username | string | Username (for successful operations). |
credentialId | string | Credential ID (for successful operations). |
userId | string | User ID (for successful operations). |
message | string | Error message (if failed). |
/CFIDE/passkey/DefaultPasskey.cfcconfigStruct.service = "/CFIDE/passkey/DefaultPasskey.cfc";/CFIDE/passkey/DatabasePasskey.cfcsecurity.setPasskeyConfig({datasource: "maria"}). Do not set variables.datasourceName directly inside DatabasePasskey.cfc. That file is overwritten on upgrade.configStruct.service = "/CFIDE/passkey/DatabasePasskey.cfc";passkey_credentials is auto-created on first use.| Setting | Options | Default |
|---|---|---|
| Challenge store | memory, ehcache, servercache | memory |
| Challenge timeout | 30–600 seconds | 60 |
adminObj = createObject("component", "CFIDE.adminapi.administrator");
adminObj.login("admin-password");
security = createObject("component", "CFIDE.adminapi.security");
config = security.getPasskeyConfig();
security.setPasskeyConfig({
challengeStore : "servercache",
challengeTtl : 120,
datasource : "maria"
});cfpasskey.js) is loaded automatically. You must call CFPasskey.startRegistration() or CFPasskey.startAuthentication() to begin the flow.function onRegisterSuccess(data, serverResult) {
// data: credential object from WebAuthn
// serverResult: server response
}
function onRegisterError(errorData) {
// errorData.error: message
// errorData.code: NOT_SUPPORTED | SAVE_FAILED | NOT_LOADED
// errorData.serverResult: optional server response
}function onAuthSuccess(assertionData, serverResult) {
// assertionData: assertion from WebAuthn
// serverResult: server response
}
function onAuthError(errorData) {
// errorData.error: message
// errorData.code: NOT_SUPPORTED | AUTH_FAILED | NOT_LOADED
// errorData.serverResult: optional server response
}redirectUrl is empty)serverResult (the public callback argument) rather than from CFPasskey._config. The leading underscore on _config marks it as a private SDK field and is not part of the supported API surface. It can change between releases.function onRegisterSuccess(data, serverResult) {
// Preferred: read the token from the server response payload.
// The exact field name depends on your backend's response shape;
// DefaultPasskey and DatabasePasskey both return it as `csrfToken`.
var token = (serverResult && serverResult.csrfToken)
|| (serverResult && serverResult.token);
if (!token) {
console.error("No passkey token returned by server");
return;
}
window.location.href = "/callback.cfm?passkey_token="
+ encodeURIComponent(token);
}
// Fallback (not recommended): CFPasskey._config.csrfToken is a private
// SDK field. It works today but may change without notice.
// var token = CFPasskey._config.csrfToken;PasskeyGetResult() and then use <cflogin> to establish the session. The password attribute is set to an empty string because passkey authentication does not use a password. The cryptographic ceremony has already verified the user's identity. The idletimeout and applicationtoken attributes for <cflogin> are inherited from <cfapplication>.<cfparam name="url.passkey_token" default="">
<cfif len(trim(url.passkey_token))>
<cfset result = PasskeyGetResult(url.passkey_token)>
<cfif result.success && result.action == "authentication">
<!--- Look up the user's actual roles from your user store --->
<cfset userRoles = getUserRoles(result.username)>
<cflogin>
<cfloginuser
name="#result.username#"
password=""
roles="#userRoles#">
</cflogin>
<cflocation url="/dashboard.cfm" addtoken="false">
</cfif>
</cfif><cfparam name="url.passkey_token" default="">
<cfif structKeyExists(url, "passkey_token") && len(trim(url.passkey_token))>
<cfset result = PasskeyGetResult(url.passkey_token)>
<cfif result.success>
<cfif result.action == "authentication">
<cfset userRoles = getUserRoles(result.username)>
<cflogin>
<cfloginuser name="#result.username#"
password=""
roles="#userRoles#">
</cflogin>
<cflocation url="/dashboard.cfm" addtoken="false">
<cfelseif result.action == "registration">
<cfset session.passkeyRegistered = true>
<cflocation url="/passkey-authenticate.cfm" addtoken="false">
</cfif>
<cfelse>
<!--- Token invalid, expired, or already consumed --->
<cfset session.passkeyError = result.message>
<cflocation url="/login.cfm" addtoken="false">
</cfif>
<cfelse>
<!--- No token: user landed here without completing a passkey flow --->
<cflocation url="/login.cfm" addtoken="false">
</cfif>CFIDE/passkey/IPasskey.cfc and override the following abstract methods:| Method | Signature |
|---|---|
savePublicKey | remote struct savePublicKey(struct credentialData, string csrfToken) |
verifyPasskeySignature | remote struct verifyPasskeySignature(struct assertionData, string csrfToken) |
getPasskeysByUser | array getPasskeysByUser(string userId) |
getPasskeysByUsername | array getPasskeysByUsername(string username) |
deletePasskey | struct deletePasskey(string userId) |
deletePasskeys | struct deletePasskeys(string userId) |
validateRegistrationResponsevalidateAssertionDatadecodeClientDataJSONvalidateCsrfTokensanitizeKeygetChallengeStoregetChallengeTtlDefaultPasskey.cfc or DatabasePasskey.cfc as a reference implementation.| Code | Description | Applies to |
|---|---|---|
NOT_SUPPORTED | Browser or device does not support WebAuthn. | Registration and authentication |
NOT_LOADED | SDK not loaded when start was called. | Registration and authentication |
SAVE_FAILED | Server failed to save the credential. | Registration only |
AUTH_FAILED | Authentication failed. User may have canceled, selected incorrect passkey, or deleted a public key. | Authentication only |
DatabasePasskey in production. File-based storage is for development and single-server only.rpId explicitly. Use cgi.server_name or your domain. Do not derive it from user input.redirectUrl. Use fixed paths or allowlisted URLs. Never use user-supplied redirect URLs.NOT_SUPPORTED, AUTH_FAILED, and SAVE_FAILED.DOMContentLoaded before calling startRegistration() or startAuthentication().result.action in callback pages. A single callback URL receives both registration and authentication tokens. Always verify the action before establishing a session.| Risk | Mitigation |
|---|---|
| Phishing | Passkeys are bound to the domain they were created for. Users cannot be tricked into entering credentials on a fake site. |
| Credential theft | Private keys never leave the device. There is no password to steal. |
| Replay attacks | WebAuthn uses challenge-response. Each authentication uses a unique challenge. Signature counter prevents replay. |
| Session fixation | Call sessionRotate() after passkey authentication to prevent session fixation. |
| CSRF | Use the built-in CSRF tokens. Do not bypass or override them. |
| rpId mismatch | Ensure rpId matches your domain. Do not use rpId from user input. |
| Redirect URL injection | Use fixed paths or allowlisted URLs for redirectUrl. Do not use user input. |
| Token reuse | Tokens are single-use. Do not reuse them after PasskeyGetResult(). |
| Insecure storage | Use DatabasePasskey in production. Avoid file-based storage for multi-server deployments. |
| Issue | Possible solution |
|---|---|
"Passkey not available" or NOT_LOADED | SDK may not be loaded. Wait for CFPasskey to be defined before calling startRegistration() or startAuthentication(). Use a retry loop or DOMContentLoaded with a short delay. |
NOT_SUPPORTED | Browser or device does not support WebAuthn. Ensure HTTPS is used (or localhost). Check browser compatibility. |
SAVE_FAILED | Server failed to save the public key. Check storage backend (file permissions, datasource, or DatabasePasskey config). |
AUTH_FAILED | Authentication failed. User may have cancelled, wrong passkey selected, or credential was deleted. |
| Invalid token or expired | Token is single-use and expires. Use it promptly after redirect. Do not refresh or bookmark the callback URL. |
Invalid rpId | rpId must be a valid hostname without protocol. Use cgi.server_name or your domain. |
Invalid redirectUrl | Use a path starting with / (for example, /callback.cfm) or a full http(s) URL. Do not use javascript: or other schemes. |
PasskeyConfigurationException | For DatabasePasskey, configure the datasource via ColdFusion Administrator > Security > Settings or security.setPasskeyConfig({datasource: "maria"}). Ensure the datasource exists and is configured. |
| Challenge timeout | Increase the challenge timeout in CF Admin or via the Admin API. Valid range is 30–600 seconds. Default is 60 seconds. |