agentsclimarketplace

Passkeys integration

Skill ColdBox/skills/security/passkeys-integration

Use this skill when implementing passkeys (WebAuthn/FIDO2) passwordless authentication in ColdBox with cbsecurity-passkeys, configuring relying party settings, building passkey registration and authentication flows, managing passkey device storage, or adding biometric and hardware security key login support.From its SKILL.md

Install
npx -y skills add ColdBox/skills --skill passkeys-integration

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

17.2 KB, ~3.7k tokens by cl100k_base, as published. Nobody here has run it

Passkeys (WebAuthn/FIDO2) in ColdBox

Overview

Passkeys provide phishing-resistant passwordless authentication using public-key cryptography. The browser/OS manages private keys (stored in secure enclave or hardware token); the server stores only public keys. ColdBox uses cbsecurity-passkeys to implement the WebAuthn ceremony.

Language Mode Reference

Examples use BoxLang (.bx) syntax by default. Adapt for your target language:

ConceptBoxLang (.bx)CFML (.cfc)
Class declarationclass [extends="..."] {component [extends="..."] {
DI annotation@inject above property name="svc";property name="svc" inject="svc";
View templates.bxm suffix.cfm / .cfml suffix
Tag prefix<bx:if>, <bx:output>, <bx:set><cfif>, <cfoutput>, <cfset>

CFML Compat Mode: With BoxLang + CFML Compat module, .bx and .cfc files coexist freely. BoxLang-native classes use class {} (.bx files); CFML-compat classes use component {} (.cfc files).

Installation

install cbsecurity
install cbsecurity-passkeys

Module Configuration

// config/ColdBox.cfc
moduleSettings = {
    "cbsecurity-passkeys": {
        rpName:                  "My Application",      // Shown during registration prompt
        rpID:                    "app.example.com",     // Must match domain (no port)
        rpOrigin:                "https://app.example.com",
        attestation:             "none",                // none | direct | indirect
        authenticatorAttachment: "platform",            // platform (biometric) | cross-platform (USB key) | "" (both)
        userVerification:        "preferred",           // required | preferred | discouraged
        storageService:          "PasskeyStorageService"  // Your custom storage impl
    }
}

Passkey Storage Service

/**
 * models/PasskeyStorageService.cfc
 * Implements the IPasskeyStorage interface from cbsecurity-passkeys.
 */
class singleton {

    // Store a newly registered passkey credential
    function storeCredential( required struct credential ) {
        queryExecute(
            "INSERT INTO passkeys
                (id, user_id, credential_id, public_key, sign_count, device_name, created_at)
             VALUES
                (:id, :userId, :credentialId, :publicKey, :signCount, :deviceName, NOW())",
            {
                id:           createUUID(),
                userId:       arguments.credential.userID,
                credentialId: arguments.credential.id,
                publicKey:    arguments.credential.publicKey,
                signCount:    arguments.credential.signCount ?: 0,
                deviceName:   arguments.credential.deviceName ?: "Unknown Device"
            }
        )
    }

    // Retrieve a credential by its WebAuthn credential ID
    function getCredential( required credentialId ) {
        var qry = queryExecute(
            "SELECT * FROM passkeys WHERE credential_id = :id",
            { id: arguments.credentialId }
        )
        if ( !qry.recordCount ) return javaCast( "null", "" )
        return queryRowToStruct( qry, 1 )
    }

    // Update sign count after successful authentication (replay attack prevention)
    function updateSignCount( required credentialId, required signCount ) {
        queryExecute(
            "UPDATE passkeys SET sign_count = :count, last_used = NOW()
             WHERE credential_id = :id",
            { count: arguments.signCount, id: arguments.credentialId }
        )
    }

    // List all passkeys for a given user
    function getCredentialsForUser( required userID ) {
        return queryExecute(
            "SELECT * FROM passkeys WHERE user_id = :userId AND revoked = 0
             ORDER BY created_at DESC",
            { userId: arguments.userID }
        )
    }

    // Revoke a specific passkey
    function revokeCredential( required credentialId ) {
        queryExecute(
            "UPDATE passkeys SET revoked = 1 WHERE credential_id = :id",
            { id: arguments.credentialId }
        )
    }
}

CFML (.cfc):

/**
 * models/PasskeyStorageService.cfc
 * Implements the IPasskeyStorage interface from cbsecurity-passkeys.
 */
component {

    // Store a newly registered passkey credential
    function storeCredential( required struct credential ) {
        queryExecute(
            "INSERT INTO passkeys
                (id, user_id, credential_id, public_key, sign_count, device_name, created_at)
             VALUES
                (:id, :userId, :credentialId, :publicKey, :signCount, :deviceName, NOW())",
            {
                id:           createUUID(),
                userId:       arguments.credential.userID,
                credentialId: arguments.credential.id,
                publicKey:    arguments.credential.publicKey,
                signCount:    arguments.credential.signCount ?: 0,
                deviceName:   arguments.credential.deviceName ?: "Unknown Device"
            }
        )
    }

    // Retrieve a credential by its WebAuthn credential ID
    function getCredential( required credentialId ) {
        var qry = queryExecute(
            "SELECT * FROM passkeys WHERE credential_id = :id",
            { id: arguments.credentialId }
        )
        if ( !qry.recordCount ) return javaCast( "null", "" )
        return queryRowToStruct( qry, 1 )
    }

    // Update sign count after successful authentication (replay attack prevention)
    function updateSignCount( required credentialId, required signCount ) {
        queryExecute(
            "UPDATE passkeys SET sign_count = :count, last_used = NOW()
             WHERE credential_id = :id",
            { count: arguments.signCount, id: arguments.credentialId }
        )
    }

    // List all passkeys for a given user
    function getCredentialsForUser( required userID ) {
        return queryExecute(
            "SELECT * FROM passkeys WHERE user_id = :userId AND revoked = 0
             ORDER BY created_at DESC",
            { userId: arguments.userID }
        )
    }

    // Revoke a specific passkey
    function revokeCredential( required credentialId ) {
        queryExecute(
            "UPDATE passkeys SET revoked = 1 WHERE credential_id = :id",
            { id: arguments.credentialId }
        )
    }
}

Passkeys Handler

/**
 * handlers/Passkeys.cfc
 */
class extends="coldbox.system.EventHandler" {

    property name="passkeyService" inject="@cbsecurity-passkeys"
    property name="cbauth"         inject="@cbauth"
    property name="userService"    inject="UserService"

    // ─── Registration ───────────────────────────────────────────────────────

    /**
     * GET /passkeys/register/options
     * Returns challenge options for the browser's navigator.credentials.create()
     */
    function registrationOptions( event, rc, prc ) {
        secured()

        var options = passkeyService.generateRegistrationOptions(
            user: prc.currentUser
        )
        return event.renderData( type = "json", data = options )
    }

    /**
     * POST /passkeys/register/verify
     * Accepts the browser's credential and stores the passkey.
     */
    function registrationVerify( event, rc, prc ) {
        secured()

        try {
            passkeyService.verifyRegistration(
                user:       prc.currentUser,
                credential: rc.credential  // JSON from browser
            )
            return event.renderData( type = "json", data = { success: true } )
        } catch ( any e ) {
            return event.renderData(
                type       = "json",
                data       = { success: false, error: e.message },
                statusCode = 400
            )
        }
    }

    // ─── Authentication ──────────────────────────────────────────────────────

    /**
     * POST /passkeys/auth/options
     * Returns the authentication challenge for navigator.credentials.get()
     * Body may optionally contain { email } to filter by user.
     */
    function authOptions( event, rc, prc ) {
        var options = passkeyService.generateAuthenticationOptions(
            email: rc.email ?: ""
        )
        return event.renderData( type = "json", data = options )
    }

    /**
     * POST /passkeys/auth/verify
     * Verifies the signed assertion from the browser; logs user in.
     */
    function authVerify( event, rc, prc ) {
        try {
            var result = passkeyService.verifyAuthentication( rc.credential )
            var user   = userService.findById( result.userID )

            cbauth.login( user )

            return event.renderData( type = "json", data = {
                success:  true,
                redirect: event.buildLink( "dashboard" )
            } )
        } catch ( any e ) {
            return event.renderData(
                type       = "json",
                data       = { success: false, error: "Authentication failed" },
                statusCode = 401
            )
        }
    }

    // ─── Device Management ───────────────────────────────────────────────────

    /**
     * GET /passkeys
     * List current user's registered passkeys.
     */
    function list( event, rc, prc ) {
        secured()
        var keys = passkeyService.listForUser( prc.currentUser.getID() )
        return event.renderData( type = "json", data = keys )
    }

    /**
     * DELETE /passkeys/:credentialID
     * Revoke a specific passkey.
     */
    function revoke( event, rc, prc ) {
        secured()
        passkeyService.revokeCredential( rc.credentialID )
        return event.renderData( type = "json", data = { message: "Passkey removed" } )
    }
}

CFML (.cfc):

/**
 * handlers/Passkeys.cfc
 */
component extends="coldbox.system.EventHandler" {

    property name="passkeyService" inject="@cbsecurity-passkeys"
    property name="cbauth"         inject="@cbauth"
    property name="userService"    inject="UserService"

    // ─── Registration ───────────────────────────────────────────────────────

    /**
     * GET /passkeys/register/options
     * Returns challenge options for the browser's navigator.credentials.create()
     */
    function registrationOptions( event, rc, prc ) {
        secured()

        var options = passkeyService.generateRegistrationOptions(
            user: prc.currentUser
        )
        return event.renderData( type = "json", data = options )
    }

    /**
     * POST /passkeys/register/verify
     * Accepts the browser's credential and stores the passkey.
     */
    function registrationVerify( event, rc, prc ) {
        secured()

        try {
            passkeyService.verifyRegistration(
                user:       prc.currentUser,
                credential: rc.credential  // JSON from browser
            )
            return event.renderData( type = "json", data = { success: true } )
        } catch ( any e ) {
            return event.renderData(
                type       = "json",
                data       = { success: false, error: e.message },
                statusCode = 400
            )
        }
    }

    // ─── Authentication ──────────────────────────────────────────────────────

    /**
     * POST /passkeys/auth/options
     * Returns the authentication challenge for navigator.credentials.get()
     * Body may optionally contain { email } to filter by user.
     */
    function authOptions( event, rc, prc ) {
        var options = passkeyService.generateAuthenticationOptions(
            email: rc.email ?: ""
        )
        return event.renderData( type = "json", data = options )
    }

    /**
     * POST /passkeys/auth/verify
     * Verifies the signed assertion from the browser; logs user in.
     */
    function authVerify( event, rc, prc ) {
        try {
            var result = passkeyService.verifyAuthentication( rc.credential )
            var user   = userService.findById( result.userID )

            cbauth.login( user )

            return event.renderData( type = "json", data = {
                success:  true,
                redirect: event.buildLink( "dashboard" )
            } )
        } catch ( any e ) {
            return event.renderData(
                type       = "json",
                data       = { success: false, error: "Authentication failed" },
                statusCode = 401
            )
        }
    }

    // ─── Device Management ───────────────────────────────────────────────────

    /**
     * GET /passkeys
     * List current user's registered passkeys.
     */
    function list( event, rc, prc ) {
        secured()
        var keys = passkeyService.listForUser( prc.currentUser.getID() )
        return event.renderData( type = "json", data = keys )
    }

    /**
     * DELETE /passkeys/:credentialID
     * Revoke a specific passkey.
     */
    function revoke( event, rc, prc ) {
        secured()
        passkeyService.revokeCredential( rc.credentialID )
        return event.renderData( type = "json", data = { message: "Passkey removed" } )
    }
}

Route Configuration

// config/Router.cfc
route( "/passkeys/register/options" ).withAction( { GET: "registrationOptions" } ).to( "passkeys" )
route( "/passkeys/register/verify"  ).withAction( { POST: "registrationVerify" } ).to( "passkeys" )
route( "/passkeys/auth/options"     ).withAction( { POST: "authOptions" } ).to( "passkeys" )
route( "/passkeys/auth/verify"      ).withAction( { POST: "authVerify" } ).to( "passkeys" )
route( "/passkeys"                  ).withAction( { GET: "list" } ).to( "passkeys" )
route( "/passkeys/:credentialID"    ).withAction( { DELETE: "revoke" } ).to( "passkeys" )

Browser JavaScript (Registration)

// Register a new passkey
async function registerPasskey() {
    // 1. Get options from server
    const optRes  = await fetch( "/passkeys/register/options" )
    const options = await optRes.json()

    // 2. Create credential in browser
    options.challenge = base64urlDecode( options.challenge )
    options.user.id   = base64urlDecode( options.user.id )

    const credential = await navigator.credentials.create( { publicKey: options } )

    // 3. Send to server for verification
    const verifyRes = await fetch( "/passkeys/register/verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify( serializeCredential( credential ) )
    } )

    const { success } = await verifyRes.json()
    if ( success ) alert( "Passkey registered!" )
}

Browser JavaScript (Authentication)

// Authenticate with passkey
async function loginWithPasskey() {
    // 1. Get challenge from server (optional email for filtering)
    const optRes  = await fetch( "/passkeys/auth/options", {
        method: "POST",
        body: JSON.stringify( { email: emailInput.value } )
    } )
    const options = await optRes.json()

    // 2. Prompt user for biometric/key
    options.challenge = base64urlDecode( options.challenge )
    const assertion   = await navigator.credentials.get( { publicKey: options } )

    // 3. Verify on server
    const verifyRes = await fetch( "/passkeys/auth/verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify( serializeCredential( assertion ) )
    } )

    const { success, redirect } = await verifyRes.json()
    if ( success ) window.location.href = redirect
}

WebAuthn Concepts Reference

TermMeaning
Relying Party (RP)Your web server/application
rpIDDomain of your app (no port)
ChallengeOne-time random bytes to prevent replay attacks
CredentialPublic key + credential ID stored on server
AuthenticatorDevice storing private key (Touch ID, Face ID, YubiKey)
AttestationProof of authenticator model (typically none for most apps)
Sign CountMonotonic counter; increases each use; detects credential cloning

Security Checklist

  • rpID matches the exact domain (no port, no subdomain unless intended)
  • rpOrigin uses HTTPS in production
  • Challenge is cryptographically random and discarded after single use
  • Sign count validated and updated on every successful auth
  • Revoked credentials cannot authenticate
  • Users can list and revoke their own passkeys
  • Passkeys complement (not replace) existing auth during rollout

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,851. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.