Securing Your Application with Passkeys and cbSecurity

ortussolutions 140 views 66 slides Jun 30, 2024
Slide 1
Slide 1 of 66
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66

About This Presentation

Discover Passkeys, the next evolution in secure login methods that eliminate traditional password vulnerabilities. Learn about the CBSecurity Passkeys module's installation, configuration, and integration into your application to enhance security.


Slide Content

https://slides.com/elpete/itb2024-cbsecurity-passkeys

Competition
Who has the most Passkeys?
https://support.google.com/chrome/answer/13168025?hl=en&co=GENIE.Platform%3DDesktop&oco=1
Ā https://support.apple.com/en-us/HT211146
https://blog.1password.com/how-save-manage-share-passkeys-1password/

What this talk is
An intro to passkeys and passwordless
authentication
Overview of the CBSecurity Passkeys
module
Demo of integrating passkeys into a
new ColdBox application

What this talk isn't
Introduction to CBSecurity
Deep Dive of all Passkeys options
Framework-agnostic (requires
CBSecurity and ColdBox)

Authentication

Username and Password
Single-use Links (Email login)
Two Factor Authentication
Security Keys or Cards
Authentication

The Problems
with Passwords

Source: The United States of P@ssw0rd$

Source: The United States of P@ssw0rd$

Source: The United States of P@ssw0rd$

Source: The United States of P@ssw0rd$ https://haveibeenpwned.com

So what's the
solution?

Password Rules?

https://neal.fun/password-game/

Password Manager?

Source: Password Manager Industry Report and Market Outlook (2023-2024)

Source: Password Manager Industry Report and Market Outlook (2023-2024)
Source: Ā (2019)The United States of P@ssw0rd$

Friction

Phishing

https://www.youtube.com/embed/u9dBGWVwMMA?si=6nSS-
4IwJBUz8Syo&clip=UgkxMBp_WBB9p6-
WqubtH0RSvSVbNCkIYULY&clipt=EMi7DhiokBI&enablejsapi=1

How do Passkeys
solve the problems
with Passwords?

No passwords to remember
Unique passkey per site
Two-factor by default
Cannot be phished
Built on Public / Private key pairs
Benefits of Passkeys

Webauthn
https://webauthn.guide/
https://webauthn.io/
MDN: Web Authentication API

Passkeys
https://passkeys.dev/
https://fidoalliance.org/passkeys/

Webauthn Passkeys==
Webauthn Passkeys!==
Techincal Name Marketing

Adoption
ā€œ
In less than a year, passkeysĀ have been used
to authenticate people more than 1 billion times
across over 400 million Google Accounts.
Passkeys, Cross-Account Protection and new ways we’re protecting your accounts

Paid Solutions
Auth0
Passage
Passwordless.dev

The "Ortus Solutions"

CBSecurity Passkeys
SUPER COOL LOGO
COMING SOON!

CBSecurity Passkeys
Builds on CBSecurity and CBAuth
Uses your Authentication Provider to log users in
Requires CBSecurity 3+ and CBAuth 6.1+
Built on top of Ā by webauthn-server-coreYubico

CBSecurity Passkeys
All required Java dependencies in a single jar
Handlers for registering new Passkeys and
authenticating using Passkeys
JavaScript resources for registering new Passkeys
and authenticating using Passkeys
Required interface for storing and retrieving
Passkeys from an external store (e.g. database)
Provides:

Installation

box install cbsecurity-passkeys

component {


this.javaSettings = {
loadPaths : [ expandPath( "./modules/cbsecurity-passkeys/lib" ) ],
loadColdFusionClassPath : true,
reloadOnChange : false
};

}1 2 3 4 5 6 7 8 9 10
Load Java
Dependencies

component {


this.javaSettings = {
loadPaths : [ expandPath( "./modules/cbsecurity-passkeys/lib" ) ],
loadColdFusionClassPath : true,
reloadOnChange : false
};

}1 2 3 4 5 6 7 8 9 10
this.javaSettings = {component {1 2 3 4 loadPaths : [ expandPath( "./modules/cbsecurity-passkeys/lib" ) ],5 loadColdFusionClassPath : true,6 reloadOnChange : false7 };8 9 }10
Load Java
Dependencies

component {


this.javaSettings = {
loadPaths : [ expandPath( "./modules/cbsecurity-passkeys/lib" ) ],
loadColdFusionClassPath : true,
reloadOnChange : false
};

}1 2 3 4 5 6 7 8 9 10
this.javaSettings = {component {1 2 3 4 loadPaths : [ expandPath( "./modules/cbsecurity-passkeys/lib" ) ],5 loadColdFusionClassPath : true,6 reloadOnChange : false7 };8 9 }10 loadColdFusionClassPath : true,
reloadOnChange : false
};component {1 2 3 this.javaSettings = {4 loadPaths : [ expandPath( "./modules/cbsecurity-passkeys/lib" ) ],5 6 7 8 9 }10
Load Java
Dependencies

component {


this.javaSettings = {
loadPaths : [ expandPath( "./modules/cbsecurity-passkeys/lib" ) ],
loadColdFusionClassPath : true,
reloadOnChange : false
};

}1 2 3 4 5 6 7 8 9 10
this.javaSettings = {component {1 2 3 4 loadPaths : [ expandPath( "./modules/cbsecurity-passkeys/lib" ) ],5 loadColdFusionClassPath : true,6 reloadOnChange : false7 };8 9 }10 loadColdFusionClassPath : true,
reloadOnChange : false
};component {1 2 3 this.javaSettings = {4 loadPaths : [ expandPath( "./modules/cbsecurity-passkeys/lib" ) ],5 6 7 8 9 }10 }component {1 2 3 this.javaSettings = {4 loadPaths : [ expandPath( "./modules/cbsecurity-passkeys/lib" ) ],5 loadColdFusionClassPath : true,6 reloadOnChange : false7 };8 9 10
Load Java
Dependencies

schema.create( "cbsecurity_passkeys" , ( t ) => {
t.increments( "id" );
t.unsignedInteger( "userId" ).references( "id" ).onTable( "users" );
t.raw( "credentialId BLOB" );
t.raw( "publicKey BLOB" );
t.unsignedInteger( "signCount" );
t.bit( "backupEligible" );
t.bit( "backupState" );
t.raw( "attestationObject BLOB" );
t.text( "clientDataJSON" );
t.datetime( "lastUsedTimestamp" ).nullable();
} );1 2 3 4 5 6 7 8 9 10 11 12
(OPTIONAL)
Migrate Database Table

schema.create( "cbsecurity_passkeys" , ( t ) => {
t.increments( "id" );
t.unsignedInteger( "userId" ).references( "id" ).onTable( "users" );
t.raw( "credentialId BLOB" );
t.raw( "publicKey BLOB" );
t.unsignedInteger( "signCount" );
t.bit( "backupEligible" );
t.bit( "backupState" );
t.raw( "attestationObject BLOB" );
t.text( "clientDataJSON" );
t.datetime( "lastUsedTimestamp" ).nullable();
} );1 2 3 4 5 6 7 8 9 10 11 12 t.unsignedInteger( "userId" ).references( "id" ).onTable( "users" );
t.raw( "credentialId BLOB" );schema.create( "cbsecurity_passkeys" , ( t ) => {1 t.increments( "id" );2 3 4 t.raw( "publicKey BLOB" );5 t.unsignedInteger( "signCount" );6 t.bit( "backupEligible" );7 t.bit( "backupState" );8 t.raw( "attestationObject BLOB" );9 t.text( "clientDataJSON" );10 t.datetime( "lastUsedTimestamp" ).nullable();11 } );12
(OPTIONAL)
Migrate Database Table

schema.create( "cbsecurity_passkeys" , ( t ) => {
t.increments( "id" );
t.unsignedInteger( "userId" ).references( "id" ).onTable( "users" );
t.raw( "credentialId BLOB" );
t.raw( "publicKey BLOB" );
t.unsignedInteger( "signCount" );
t.bit( "backupEligible" );
t.bit( "backupState" );
t.raw( "attestationObject BLOB" );
t.text( "clientDataJSON" );
t.datetime( "lastUsedTimestamp" ).nullable();
} );1 2 3 4 5 6 7 8 9 10 11 12 t.unsignedInteger( "userId" ).references( "id" ).onTable( "users" );
t.raw( "credentialId BLOB" );schema.create( "cbsecurity_passkeys" , ( t ) => {1 t.increments( "id" );2 3 4 t.raw( "publicKey BLOB" );5 t.unsignedInteger( "signCount" );6 t.bit( "backupEligible" );7 t.bit( "backupState" );8 t.raw( "attestationObject BLOB" );9 t.text( "clientDataJSON" );10 t.datetime( "lastUsedTimestamp" ).nullable();11 } );12 t.unsignedInteger( "signCount" );
t.bit( "backupEligible" );
t.bit( "backupState" );schema.create( "cbsecurity_passkeys" , ( t ) => {1 t.increments( "id" );2 t.unsignedInteger( "userId" ).references( "id" ).onTable( "users" );3 t.raw( "credentialId BLOB" );4 t.raw( "publicKey BLOB" );5 6 7 8 t.raw( "attestationObject BLOB" );9 t.text( "clientDataJSON" );10 t.datetime( "lastUsedTimestamp" ).nullable();11 } );12
(OPTIONAL)
Migrate Database Table

schema.create( "cbsecurity_passkeys" , ( t ) => {
t.increments( "id" );
t.unsignedInteger( "userId" ).references( "id" ).onTable( "users" );
t.raw( "credentialId BLOB" );
t.raw( "publicKey BLOB" );
t.unsignedInteger( "signCount" );
t.bit( "backupEligible" );
t.bit( "backupState" );
t.raw( "attestationObject BLOB" );
t.text( "clientDataJSON" );
t.datetime( "lastUsedTimestamp" ).nullable();
} );1 2 3 4 5 6 7 8 9 10 11 12 t.unsignedInteger( "userId" ).references( "id" ).onTable( "users" );
t.raw( "credentialId BLOB" );schema.create( "cbsecurity_passkeys" , ( t ) => {1 t.increments( "id" );2 3 4 t.raw( "publicKey BLOB" );5 t.unsignedInteger( "signCount" );6 t.bit( "backupEligible" );7 t.bit( "backupState" );8 t.raw( "attestationObject BLOB" );9 t.text( "clientDataJSON" );10 t.datetime( "lastUsedTimestamp" ).nullable();11 } );12 t.unsignedInteger( "signCount" );
t.bit( "backupEligible" );
t.bit( "backupState" );schema.create( "cbsecurity_passkeys" , ( t ) => {1 t.increments( "id" );2 t.unsignedInteger( "userId" ).references( "id" ).onTable( "users" );3 t.raw( "credentialId BLOB" );4 t.raw( "publicKey BLOB" );5 6 7 8 t.raw( "attestationObject BLOB" );9 t.text( "clientDataJSON" );10 t.datetime( "lastUsedTimestamp" ).nullable();11 } );12 t.text( "clientDataJSON" );
t.datetime( "lastUsedTimestamp" ).nullable();
} );schema.create( "cbsecurity_passkeys" , ( t ) => {1 t.increments( "id" );2 t.unsignedInteger( "userId" ).references( "id" ).onTable( "users" );3 t.raw( "credentialId BLOB" );4 t.raw( "publicKey BLOB" );5 t.unsignedInteger( "signCount" );6 t.bit( "backupEligible" );7 t.bit( "backupState" );8 t.raw( "attestationObject BLOB" );9 10 11 12
(OPTIONAL)
Migrate Database Table

Configuration

Module Settings

moduleSettings = {
"cbsecurity-passkeys" : {
// WireBox mapping to the component
// implementing the ICredentialRepository interface
"credentialRepositoryMapping" : "",

// The identifier for the relying party (your application)
"relyingPartyId" : CGI.SERVER_NAME,

// The display name for the relying party (your application)
"relyingPartyName" : controller.getSetting( "appName" ),

// The allowed origins to send you Passkeys.
// Defaults to the server name, if empty.
// (Non-standard ports need to be specified explicitly)
"allowedOrigins": []
}
};1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

moduleSettings = {
"cbsecurity-passkeys" : {
// WireBox mapping to the component
// implementing the ICredentialRepository interface
"credentialRepositoryMapping" : "",

// The identifier for the relying party (your application)
"relyingPartyId" : CGI.SERVER_NAME,

// The display name for the relying party (your application)
"relyingPartyName" : controller.getSetting( "appName" ),

// The allowed origins to send you Passkeys.
// Defaults to the server name, if empty.
// (Non-standard ports need to be specified explicitly)
"allowedOrigins": []
}
};1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // WireBox mapping to the component
// implementing the ICredentialRepository interface
"credentialRepositoryMapping" : "",moduleSettings = {1 "cbsecurity-passkeys" : {2 3 4 5 6 // The identifier for the relying party (your application)7 "relyingPartyId" : CGI.SERVER_NAME,8 9 // The display name for the relying party (your application)10 "relyingPartyName" : controller.getSetting( "appName" ),11 12 // The allowed origins to send you Passkeys.13 // Defaults to the server name, if empty.14 // (Non-standard ports need to be specified explicitly)15 "allowedOrigins": []16 }17 };18

moduleSettings = {
"cbsecurity-passkeys" : {
// WireBox mapping to the component
// implementing the ICredentialRepository interface
"credentialRepositoryMapping" : "",

// The identifier for the relying party (your application)
"relyingPartyId" : CGI.SERVER_NAME,

// The display name for the relying party (your application)
"relyingPartyName" : controller.getSetting( "appName" ),

// The allowed origins to send you Passkeys.
// Defaults to the server name, if empty.
// (Non-standard ports need to be specified explicitly)
"allowedOrigins": []
}
};1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // WireBox mapping to the component
// implementing the ICredentialRepository interface
"credentialRepositoryMapping" : "",moduleSettings = {1 "cbsecurity-passkeys" : {2 3 4 5 6 // The identifier for the relying party (your application)7 "relyingPartyId" : CGI.SERVER_NAME,8 9 // The display name for the relying party (your application)10 "relyingPartyName" : controller.getSetting( "appName" ),11 12 // The allowed origins to send you Passkeys.13 // Defaults to the server name, if empty.14 // (Non-standard ports need to be specified explicitly)15 "allowedOrigins": []16 }17 };18 // The identifier for the relying party (your application)
"relyingPartyId" : CGI.SERVER_NAME,

// The display name for the relying party (your application)moduleSettings = {1 "cbsecurity-passkeys" : {2 // WireBox mapping to the component3 // implementing the ICredentialRepository interface4 "credentialRepositoryMapping" : "",5 6 7 8 9 10 "relyingPartyName" : controller.getSetting( "appName" ),11 12 // The allowed origins to send you Passkeys.13 // Defaults to the server name, if empty.14 // (Non-standard ports need to be specified explicitly)15 "allowedOrigins": []16 }17 };18

moduleSettings = {
"cbsecurity-passkeys" : {
// WireBox mapping to the component
// implementing the ICredentialRepository interface
"credentialRepositoryMapping" : "",

// The identifier for the relying party (your application)
"relyingPartyId" : CGI.SERVER_NAME,

// The display name for the relying party (your application)
"relyingPartyName" : controller.getSetting( "appName" ),

// The allowed origins to send you Passkeys.
// Defaults to the server name, if empty.
// (Non-standard ports need to be specified explicitly)
"allowedOrigins": []
}
};1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // WireBox mapping to the component
// implementing the ICredentialRepository interface
"credentialRepositoryMapping" : "",moduleSettings = {1 "cbsecurity-passkeys" : {2 3 4 5 6 // The identifier for the relying party (your application)7 "relyingPartyId" : CGI.SERVER_NAME,8 9 // The display name for the relying party (your application)10 "relyingPartyName" : controller.getSetting( "appName" ),11 12 // The allowed origins to send you Passkeys.13 // Defaults to the server name, if empty.14 // (Non-standard ports need to be specified explicitly)15 "allowedOrigins": []16 }17 };18 // The identifier for the relying party (your application)
"relyingPartyId" : CGI.SERVER_NAME,

// The display name for the relying party (your application)moduleSettings = {1 "cbsecurity-passkeys" : {2 // WireBox mapping to the component3 // implementing the ICredentialRepository interface4 "credentialRepositoryMapping" : "",5 6 7 8 9 10 "relyingPartyName" : controller.getSetting( "appName" ),11 12 // The allowed origins to send you Passkeys.13 // Defaults to the server name, if empty.14 // (Non-standard ports need to be specified explicitly)15 "allowedOrigins": []16 }17 };18
// The allowed origins to send you Passkeys.
// Defaults to the server name, if empty.moduleSettings = {1 "cbsecurity-passkeys" : {2 // WireBox mapping to the component3 // implementing the ICredentialRepository interface4 "credentialRepositoryMapping" : "",5 6 // The identifier for the relying party (your application)7 "relyingPartyId" : CGI.SERVER_NAME,8 9 // The display name for the relying party (your application)10 "relyingPartyName" : controller.getSetting( "appName" ),11 12 13 14 // (Non-standard ports need to be specified explicitly)15 "allowedOrigins": []16 }17 };18

ICredentialRepository

ICredentialRepository
Defines the methods needed to
register and authenticate Passkeys
An example implementation using
Quick is available in the module.
resources/examples/Passkey.cfc

ICredentialRepository
public string function getUsernameForUser( required any user ) {}

public string function getDisplayNameForUser ( required any user ) {}

public any function getUserHandleForUser ( required any user ) {}

public array function getCredentialIdsForUsername ( required string username ) {}

public any function getUserHandleForUsername ( required string username ) {}

public string function getUsernameForUserHandle ( required any userHandle ) {}

public struct function lookup( required any credentialId, required any userHandle ) {}

public array function lookupAll( required any credentialId ) {}

public void function storeCredentialForUser ( /* ... */ ) {}

public void function updateCredentialForUser ( /* ... */ ) {}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

Usage

Server-side

Implement
ICredentialRepository

Client-side

Detecting Passkey Support
<cfoutput>
<div>
<div class="jumbotron mt-sm-5 p-4" >
<h1>Passkey Demo</h1>
</div>
<form id="passkey-form" action="#event.buildLink( " passkeys.new" )#" method=
<button type="submit" class="btn btn-primary">Create Passkey</button>
</form>
</div>
</cfoutput>

<script src="/modules_app/cbsecurity-passkeys/includes/js/passkeys.js" ></script>
<script>
if (window.cbSecurity.passkeys.isSupported()) {
document.getElementById("passkey-form").style.display = "block";
}
</script>1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

Registering Passkeys
<cfoutput>
<div>
<div class="jumbotron mt-sm-5 p-4" >
<h1>Creating a Passkey... </h1>
</div>
</div>
<script src="/modules/cbsecurity-passkeys/includes/passkeys.js" ></script>
<script>
window.cbSecurity.passkeys.register();
</script>
</cfoutput>1 2 3 4 5 6 7 8 9 10 11

Logging in with a Passkey
<cfoutput>
<h3>Log In</h3>
<small>or <a href="#event.buildLink( " registrations.new" )#">register for an account </a></small>
<hr />
<form id="loginForm" method="POST" action="#event.buildLink( " login" )#">
<input type="hidden" name="_token" value="#csrfGenerateToken()#" />
<div class="form-group">
<label for="username">Email Address:</label>
<input name="email" type="text" class="form-control" id="email" autocomplete="username webauthn" />
</div>
<div class="form-group">
<label for="password">Password:</label>
<input name="password" type="password" class="form-control" id="password" autocomplete="current-password webauthn" />
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Log In</button>
</div>
</form>
<button id="passkeyLoginButton" type="button" class="btn btn-primary" style="display: none;">Log in with Passkey</button>
</cfoutput>

<script src="/modules_app/cbsecurity-passkeys/includes/passkeys.js" ></script>
<script>
if ( window.cbSecurity.passkeys.isSupported() ) {
const passkeyLoginButton = document.getElementById( "passkeyLoginButton" );
passkeyLoginButton.style.display = "block";
passkeyLoginButton.addEventListener( "click", function() {
window.cbSecurity.passkeys.login( document.forms.loginForm.email.value);
});
}
</script>1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

Demo

Competition
Results
So who has the most Passkeys?