Two factor authentication
Skill ColdBox/skills/contentbox-cfml/two-factor-authentication
Collection of skills for the ColdBox Platform and Claude Plugin
npx -y skills add ColdBox/skills --skill two-factor-authenticationAssembled 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.
What its author says it does
Copied from the file, not written here
Use this skill when implementing or customizing ContentBox two-factor authentication providers, trusted device flows, enrollment/verification UX, enforcement policies, and provider lifecycle handling.
SKILL.md
9.5 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
ContentBox Two-Factor Authentication (CFML)
Implement and extend two-factor authentication (2FA) in ContentBox CMS using CFML. ContentBox provides a pluggable 2FA system with provider interfaces, trusted device support, and global enforcement.
2FA Architecture
The 2FA system lives at modules/contentbox/models/security/twofactor/:
| Component | File | Purpose |
|---|---|---|
| Interface | ITwoFactorProvider.cfc | Contract all 2FA providers must implement |
| Base Class | BaseTwoFactorProvider.cfc | Base class with injected services |
| Service | TwoFactorService.cfc | Provider registry and orchestration |
TwoFactorService
The central service manages 2FA providers and settings:
property name="twoFactorService" inject="twoFactorService@contentbox";
// Provider management
twoFactorService.registerProvider( provider )
twoFactorService.unRegisterProvider( name )
twoFactorService.getRegisteredProviders()
twoFactorService.getRegisteredProvidersWithDisplayNames()
twoFactorService.getProvider( name )
// Settings
twoFactorService.isForceTwoFactorAuth()
twoFactorService.getDefaultProvider()
twoFactorService.getDefaultProviderObject()
twoFactorService.getTrustedDeviceTimespan()
2FA Settings
Stored in the cb_setting table:
| Setting Key | Description |
|---|---|
cb_security_2factorAuth_force | Force global 2FA enrollment (true/false) |
cb_security_2factorAuth_provider | Default provider name |
cb_security_2factorAuth_trusted_days | Trusted device cookie duration (days) |
Trusted Device Cookie
variables.TRUSTED_DEVICE_COOKIE = "contentbox_2factor_device"
When allowTrustedDevice() returns true, a cookie is set. If the user logs in from the same device within the trusted timespan, 2FA validation is skipped.
ITwoFactorProvider Interface
All 2FA providers must implement this interface:
interface {
/**
* Get the internal name of a provider, used for registration, internal naming and more.
*/
function getName();
/**
* Get the display name for the provider. Used in all UI screens.
*/
function getDisplayName();
/**
* Returns HTML to display to the user for required two-factor fields.
*/
function getAuthorSetupForm( required author );
/**
* Get the display help for the provider. Used in the UI setup screens for the author.
*/
function getAuthorSetupHelp( required author );
/**
* Get the verification help for the provider. Used in the UI verification screen.
*/
function getVerificationHelp();
/**
* If true, ContentBox will set a tracking cookie for the user's browser.
* If the user logs in and the device is within the trusted timespan,
* no two-factor authentication validation will occur.
*/
boolean function allowTrustedDevice();
/**
* Send a challenge via the 2 factor auth implementation.
*
* @author The author to challenge
* @return struct:{ error:boolean, messages=string }
*/
struct function sendChallenge( required author );
/**
* Verify a challenge for the specific user.
*
* @code The verification code
* @author The author to verify challenge
* @return struct:{ error:boolean, messages:string }
*/
struct function verifyChallenge( required string code, required author );
/**
* Called once a two factor challenge is accepted and valid.
* The user has completed validation and will be logged in.
*
* @code The verification code
* @author The author to verify challenge
*/
function finalize( required string code, required author );
}
Creating a Custom 2FA Provider
Base Provider
Extend BaseTwoFactorProvider for auto-injected services:
<!--- models/security/MyTOTPProvider.cfc --->
<cfcomponent
extends="contentbox.models.security.twofactor.BaseTwoFactorProvider"
singleton
implements="contentbox.models.security.twofactor.ITwoFactorProvider"
>
<cffunction name="init" access="public" returntype="MyTOTPProvider">
<cfreturn this>
</cffunction>
<cffunction name="getName" access="public" returntype="string">
<cfreturn "mytotp">
</cffunction>
<cffunction name="getDisplayName" access="public" returntype="string">
<cfreturn "My TOTP Authenticator">
</cffunction>
<cffunction name="getAuthorSetupForm" access="public" returntype="string">
<cfargument name="author" type="any">
<cfreturn "
<div class='form-group'>
<label>Secret Key</label>
<input type='text' name='totp_secret' class='form-control'
value='#author.getTwoFactorSecret() ?: ''#'>
</div>
">
</cffunction>
<cffunction name="getAuthorSetupHelp" access="public" returntype="string">
<cfargument name="author" type="any">
<cfreturn "<p>Scan the QR code with your authenticator app.</p>">
</cffunction>
<cffunction name="getVerificationHelp" access="public" returntype="string">
<cfreturn "<p>Enter the 6-digit code from your authenticator app.</p>">
</cffunction>
<cffunction name="allowTrustedDevice" access="public" returntype="boolean">
<cfreturn true>
</cffunction>
<cffunction name="sendChallenge" access="public" returntype="struct">
<cfargument name="author" type="any">
<!--- Send the challenge (e.g., generate TOTP, send SMS, etc.) --->
<cfreturn { error : false, messages : "Challenge sent successfully." }>
</cffunction>
<cffunction name="verifyChallenge" access="public" returntype="struct">
<cfargument name="code" type="string">
<cfargument name="author" type="any">
<!--- Verify the code --->
<cfset var isValid = verifyTOTP( arguments.code, arguments.author.getTwoFactorSecret() )>
<cfif isValid>
<cfreturn { error : false, messages : "Code verified." }>
<cfelse>
<cfreturn { error : true, messages : "Invalid code. Please try again." }>
</cfif>
</cffunction>
<cffunction name="finalize" access="public" returntype="void">
<cfargument name="code" type="string">
<cfargument name="author" type="any">
<!--- Called after successful validation --->
<cfset log.info( "2FA finalized for author: #author.getUsername()#" )>
</cffunction>
<!--- Private helper --->
<cffunction name="verifyTOTP" access="private" returntype="boolean">
<cfargument name="code" type="string">
<cfargument name="secret" type="string">
<!--- TOTP verification logic --->
<cfreturn true>
</cffunction>
</cfcomponent>
Registering a 2FA Provider
Register your provider in your module's onLoad():
<cffunction name="onLoad" access="public" returntype="void">
<cfset var twoFactorService = wirebox.getInstance( "twoFactorService@contentbox" )>
<cfset twoFactorService.registerProvider(
wirebox.getInstance( "MyTOTPProvider@mymodule" )
)>
</cffunction>
2FA Enforcement
Global Enforcement
The CheckForForceTwoFactorEnrollment interceptor enforces 2FA globally:
- Runs on
preProcessfor admin requests - Excludes security module events (login, logout, password reset)
- Redirects unenrolled users to
cbadmin/security/twofactorEnrollment/forceEnrollment
Provider Change Handling
The UnenrollTwoFactorOnProviderChange interceptor handles provider changes:
- When the default 2FA provider is changed in settings
- Unenrolls all users from 2FA (they must re-enroll with the new provider)
Author 2FA Properties
The Author entity stores 2FA-related data:
<!--- Author entity 2FA properties --->
<cfproperty name="twoFactorEnabled" ormtype="boolean" default="false">
<cfproperty name="twoFactorSecret" ormtype="string" length="500">
<cfproperty name="twoFactorProvider" ormtype="string" length="100">
Checking 2FA Status
property name="authorService" inject="authorService@contentbox";
property name="twoFactorService" inject="twoFactorService@contentbox";
// Check if global 2FA is forced
<cfif twoFactorService.isForceTwoFactorAuth()>
<!--- All users must enroll --->
</cfif>
// Check if a specific author has 2FA enabled
<cfif author.getTwoFactorEnabled()>
<!--- Author has 2FA enabled --->
</cfif>
// Get the default provider
<cfset var provider = twoFactorService.getDefaultProviderObject()>
Built-in 2FA Providers
ContentBox ships with these providers:
| Provider | Description |
|---|---|
| TOTP | Time-based One-Time Password (RFC 6238) — works with Google Authenticator, Authy, etc. |
Best Practices
- Implement
ITwoFactorProvider— all methods are required - Extend
BaseTwoFactorProvider— provides DI forlog,settingService,securityService,siteService,renderer,CBHelper - Return proper structs —
sendChallenge()andverifyChallenge()must return{ error, messages } - Use
singletonscope — providers are instantiated once - Register in
onLoad()— register providers after module configuration - Support trusted devices — implement
allowTrustedDevice()for better UX - Store secrets securely — use encryption for sensitive data
- Provide clear help text —
getAuthorSetupHelp()andgetVerificationHelp()guide users - Handle errors gracefully — return meaningful error messages
- Test enrollment flow — verify the full enrollment and verification cycle
Engine Compatibility
This skill targets CFML engines (Lucee 5+, Adobe ColdFusion 2018+). For BoxLang-specific syntax and features, see the BoxLang variant of this skill.