or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdauthorization.mdcore-setup.mddatabase.mdindex.mdpassword-management.mdregistration.mdtwo-factor.mdunified-signin.mdutilities.mdwebauthn.md

webauthn.mddocs/

0

# WebAuthn Support

1

2

WebAuthn/FIDO2 authentication for passwordless and phishing-resistant authentication using hardware security keys, biometrics, and platform authenticators in Flask applications.

3

4

## Capabilities

5

6

### WebAuthn Forms

7

8

Forms for managing WebAuthn credential registration, signin, and administration.

9

10

```python { .api }

11

class WebAuthnRegisterForm(Form):

12

"""

13

WebAuthn credential registration initiation form.

14

15

Fields:

16

- usage: Usage type for credential ('first', 'secondary')

17

- name: Human-readable name for the credential

18

"""

19

usage: SelectField

20

name: StringField

21

22

class WebAuthnRegisterResponseForm(Form):

23

"""

24

WebAuthn credential registration completion form.

25

26

Fields:

27

- credential: JSON credential response from authenticator

28

- name: Human-readable name for the credential

29

"""

30

credential: HiddenField

31

name: StringField

32

33

class WebAuthnSigninForm(Form):

34

"""

35

WebAuthn signin initiation form.

36

37

Fields:

38

- identity: User identity (email/username) for credential lookup

39

"""

40

identity: StringField

41

42

class WebAuthnSigninResponseForm(Form):

43

"""

44

WebAuthn signin completion form.

45

46

Fields:

47

- credential: JSON credential assertion from authenticator

48

"""

49

credential: HiddenField

50

51

class WebAuthnDeleteForm(Form):

52

"""

53

WebAuthn credential deletion form.

54

55

Fields:

56

- credential_id: ID of credential to delete

57

"""

58

credential_id: HiddenField

59

60

class WebAuthnVerifyForm(Form):

61

"""

62

WebAuthn verification form for sensitive operations.

63

64

Fields:

65

- credential: JSON credential assertion for verification

66

"""

67

credential: HiddenField

68

```

69

70

### WebAuthn User Mixin

71

72

Mixin providing WebAuthn credential management methods for user models.

73

74

```python { .api }

75

class WebAuthnMixin:

76

"""

77

Mixin for user models providing WebAuthn credential interface.

78

"""

79

80

@property

81

def webauthn_credentials(self):

82

"""

83

Return list of WebAuthn credentials for user.

84

85

Returns:

86

List of WebAuthnCredential objects associated with user

87

"""

88

89

def get_webauthn_credential_by_id(self, credential_id):

90

"""

91

Get WebAuthn credential by credential ID.

92

93

Parameters:

94

- credential_id: Base64-encoded credential ID to search for

95

96

Returns:

97

WebAuthnCredential object if found, None otherwise

98

"""

99

100

def get_webauthn_credential_options(self):

101

"""

102

Get options for WebAuthn credential creation.

103

104

Returns:

105

Dictionary containing credential creation options including:

106

- rp: Relying party information

107

- user: User information for credential

108

- challenge: Random challenge bytes

109

- pubKeyCredParams: Supported algorithms

110

- excludeCredentials: Existing credentials to exclude

111

"""

112

```

113

114

### WebAuthn Utility Class

115

116

Utility class for WebAuthn operations and credential management.

117

118

```python { .api }

119

class WebauthnUtil:

120

"""

121

WebAuthn operations and credential management utility class.

122

"""

123

124

def __init__(self, app=None):

125

"""

126

Initialize WebAuthn utility.

127

128

Parameters:

129

- app: Flask application instance (optional)

130

"""

131

132

def init_app(self, app):

133

"""

134

Initialize WebAuthn utility with Flask app.

135

136

Parameters:

137

- app: Flask application instance

138

"""

139

140

def generate_challenge(self):

141

"""

142

Generate cryptographic challenge for WebAuthn operations.

143

144

Returns:

145

Base64-encoded challenge bytes

146

"""

147

148

def get_credential_creation_options(self, user):

149

"""

150

Get credential creation options for user registration.

151

152

Parameters:

153

- user: User object for credential creation

154

155

Returns:

156

Dictionary with credential creation options

157

"""

158

159

def get_credential_request_options(self, user=None):

160

"""

161

Get credential request options for authentication.

162

163

Parameters:

164

- user: User object (optional, for usernameless authentication)

165

166

Returns:

167

Dictionary with credential request options

168

"""

169

170

def register_credential(self, user, credential_response, name=None):

171

"""

172

Register new WebAuthn credential for user.

173

174

Parameters:

175

- user: User object to associate credential with

176

- credential_response: Credential response from authenticator

177

- name: Human-readable name for credential (optional)

178

179

Returns:

180

WebAuthnCredential object if registration successful

181

182

Raises:

183

ValidationError if credential registration fails

184

"""

185

186

def authenticate_credential(self, credential_response, user=None):

187

"""

188

Authenticate using WebAuthn credential assertion.

189

190

Parameters:

191

- credential_response: Credential assertion from authenticator

192

- user: User object for verification (optional)

193

194

Returns:

195

Tuple of (user, credential) if authentication successful,

196

(None, None) otherwise

197

"""

198

199

def delete_credential(self, user, credential_id):

200

"""

201

Delete WebAuthn credential for user.

202

203

Parameters:

204

- user: User object owning the credential

205

- credential_id: ID of credential to delete

206

207

Returns:

208

True if credential deleted successfully, False otherwise

209

"""

210

```

211

212

### WebAuthn Plugin for Two-Factor Authentication

213

214

Plugin integrating WebAuthn with Flask-Security's two-factor authentication system.

215

216

```python { .api }

217

class WebAuthnTfPlugin(TfPluginBase):

218

"""

219

WebAuthn two-factor authentication plugin.

220

"""

221

222

def get_setup_form(self):

223

"""

224

Get form for WebAuthn 2FA setup.

225

226

Returns:

227

WebAuthnRegisterForm class for credential registration

228

"""

229

230

def get_verify_form(self):

231

"""

232

Get form for WebAuthn 2FA verification.

233

234

Returns:

235

WebAuthnVerifyForm class for credential verification

236

"""

237

238

def setup_validate(self, form):

239

"""

240

Validate WebAuthn 2FA setup.

241

242

Parameters:

243

- form: WebAuthnRegisterResponseForm with credential data

244

245

Returns:

246

True if setup validation successful, False otherwise

247

"""

248

249

def verify(self, form):

250

"""

251

Verify WebAuthn 2FA credential.

252

253

Parameters:

254

- form: WebAuthnVerifyForm with credential assertion

255

256

Returns:

257

True if verification successful, False otherwise

258

"""

259

260

def delete(self, totp_secret):

261

"""

262

Delete WebAuthn 2FA credential.

263

264

Parameters:

265

- totp_secret: Credential identifier for deletion

266

267

Returns:

268

True if deletion successful, False otherwise

269

"""

270

```

271

272

## Configuration

273

274

WebAuthn support is configured through Flask-Security configuration variables:

275

276

```python

277

# Enable WebAuthn support

278

app.config['SECURITY_WEBAUTHN'] = True

279

280

# WebAuthn relying party configuration

281

app.config['SECURITY_WEBAUTHN_RP_NAME'] = 'Your App Name'

282

app.config['SECURITY_WEBAUTHN_RP_ID'] = 'example.com'

283

284

# Challenge configuration

285

app.config['SECURITY_WEBAUTHN_CHALLENGE_TTL'] = 300 # 5 minutes

286

287

# Credential algorithms (default includes ES256, PS256, RS256)

288

app.config['SECURITY_WEBAUTHN_ALGORITHMS'] = ['ES256', 'PS256', 'RS256']

289

290

# Authenticator attachment preference

291

app.config['SECURITY_WEBAUTHN_AUTHENTICATOR_ATTACHMENT'] = 'cross-platform'

292

293

# User verification requirement

294

app.config['SECURITY_WEBAUTHN_USER_VERIFICATION'] = 'preferred'

295

```

296

297

## Usage Examples

298

299

### Basic WebAuthn Setup

300

301

```python

302

from flask import Flask

303

from flask_security import Security, WebAuthnMixin, UserMixin

304

from flask_sqlalchemy import SQLAlchemy

305

306

app = Flask(__name__)

307

app.config['SECURITY_WEBAUTHN'] = True

308

app.config['SECURITY_WEBAUTHN_RP_NAME'] = 'My Secure App'

309

app.config['SECURITY_WEBAUTHN_RP_ID'] = 'myapp.com'

310

311

db = SQLAlchemy(app)

312

313

class User(db.Model, UserMixin, WebAuthnMixin):

314

id = db.Column(db.Integer, primary_key=True)

315

email = db.Column(db.String(255), unique=True)

316

password = db.Column(db.String(255))

317

active = db.Column(db.Boolean(), default=True)

318

319

class WebAuthnCredential(db.Model):

320

id = db.Column(db.Integer, primary_key=True)

321

user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

322

credential_id = db.Column(db.Text, unique=True)

323

public_key = db.Column(db.Text)

324

name = db.Column(db.String(100))

325

usage = db.Column(db.String(20))

326

created_at = db.Column(db.DateTime)

327

328

security = Security(app, user_datastore)

329

```

330

331

### Credential Registration

332

333

```python

334

from flask_security import current_user, login_required

335

336

@app.route('/register-webauthn')

337

@login_required

338

def register_webauthn():

339

# Display WebAuthn registration form

340

return render_template('webauthn_register.html')

341

342

@app.route('/webauthn/register/begin', methods=['POST'])

343

@login_required

344

def webauthn_register_begin():

345

# Get credential creation options

346

options = current_user.get_webauthn_credential_options()

347

session['webauthn_challenge'] = options['challenge']

348

return jsonify(options)

349

350

@app.route('/webauthn/register/complete', methods=['POST'])

351

@login_required

352

def webauthn_register_complete():

353

credential_response = request.json

354

challenge = session.get('webauthn_challenge')

355

356

try:

357

# Register the credential

358

webauthn_util = current_app.extensions['security'].webauthn_util

359

credential = webauthn_util.register_credential(

360

current_user,

361

credential_response,

362

name=request.form.get('name', 'Security Key')

363

)

364

365

return jsonify({'success': True, 'credential_id': credential.id})

366

except ValidationError as e:

367

return jsonify({'success': False, 'error': str(e)}), 400

368

```

369

370

### WebAuthn Authentication

371

372

```python

373

@app.route('/webauthn-signin')

374

def webauthn_signin():

375

return render_template('webauthn_signin.html')

376

377

@app.route('/webauthn/authenticate/begin', methods=['POST'])

378

def webauthn_authenticate_begin():

379

identity = request.form.get('identity')

380

user = lookup_identity(identity) if identity else None

381

382

# Get authentication options

383

webauthn_util = current_app.extensions['security'].webauthn_util

384

options = webauthn_util.get_credential_request_options(user)

385

session['webauthn_challenge'] = options['challenge']

386

387

return jsonify(options)

388

389

@app.route('/webauthn/authenticate/complete', methods=['POST'])

390

def webauthn_authenticate_complete():

391

credential_response = request.json

392

challenge = session.get('webauthn_challenge')

393

394

try:

395

webauthn_util = current_app.extensions['security'].webauthn_util

396

user, credential = webauthn_util.authenticate_credential(credential_response)

397

398

if user:

399

login_user(user)

400

return jsonify({'success': True, 'redirect': '/dashboard'})

401

else:

402

return jsonify({'success': False, 'error': 'Authentication failed'}), 401

403

404

except ValidationError as e:

405

return jsonify({'success': False, 'error': str(e)}), 400

406

```

407

408

### WebAuthn as Two-Factor Authentication

409

410

```python

411

from flask_security import TwoFactorSetupForm

412

413

# Enable WebAuthn 2FA plugin

414

app.config['SECURITY_TWO_FACTOR_ENABLED_METHODS'] = ['authenticator', 'webauthn']

415

416

@app.route('/setup-2fa-webauthn')

417

@login_required

418

def setup_2fa_webauthn():

419

"""Setup WebAuthn as second factor."""

420

if not current_user.tf_totp_secret:

421

# User needs to complete initial 2FA setup first

422

return redirect(url_for_security('two_factor_setup'))

423

424

return render_template('setup_webauthn_2fa.html')

425

426

@app.route('/verify-2fa-webauthn')

427

@login_required

428

def verify_2fa_webauthn():

429

"""Verify WebAuthn credential for 2FA."""

430

return render_template('verify_webauthn_2fa.html')

431

```

432

433

### Credential Management

434

435

```python

436

@app.route('/manage-webauthn')

437

@login_required

438

def manage_webauthn_credentials():

439

"""Display user's WebAuthn credentials."""

440

credentials = current_user.webauthn_credentials

441

return render_template('manage_webauthn.html', credentials=credentials)

442

443

@app.route('/delete-webauthn/<credential_id>', methods=['POST'])

444

@login_required

445

def delete_webauthn_credential(credential_id):

446

"""Delete a WebAuthn credential."""

447

webauthn_util = current_app.extensions['security'].webauthn_util

448

449

if webauthn_util.delete_credential(current_user, credential_id):

450

flash('Security key removed successfully')

451

else:

452

flash('Failed to remove security key')

453

454

return redirect(url_for('manage_webauthn_credentials'))

455

```

456

457

### JavaScript Integration

458

459

```javascript

460

// WebAuthn credential registration

461

async function registerWebAuthnCredential() {

462

try {

463

// Get registration options from server

464

const optionsResponse = await fetch('/webauthn/register/begin', {

465

method: 'POST',

466

headers: {'Content-Type': 'application/json'},

467

});

468

const options = await optionsResponse.json();

469

470

// Convert base64 strings to ArrayBuffers

471

options.challenge = base64ToArrayBuffer(options.challenge);

472

options.user.id = base64ToArrayBuffer(options.user.id);

473

474

// Create credential

475

const credential = await navigator.credentials.create({

476

publicKey: options

477

});

478

479

// Send credential to server

480

const registrationResponse = await fetch('/webauthn/register/complete', {

481

method: 'POST',

482

headers: {'Content-Type': 'application/json'},

483

body: JSON.stringify({

484

id: credential.id,

485

rawId: arrayBufferToBase64(credential.rawId),

486

response: {

487

clientDataJSON: arrayBufferToBase64(credential.response.clientDataJSON),

488

attestationObject: arrayBufferToBase64(credential.response.attestationObject)

489

}

490

})

491

});

492

493

const result = await registrationResponse.json();

494

if (result.success) {

495

alert('Security key registered successfully!');

496

} else {

497

alert('Registration failed: ' + result.error);

498

}

499

500

} catch (error) {

501

console.error('WebAuthn registration error:', error);

502

alert('WebAuthn registration failed');

503

}

504

}

505

506

// WebAuthn authentication

507

async function authenticateWithWebAuthn() {

508

try {

509

// Get authentication options

510

const optionsResponse = await fetch('/webauthn/authenticate/begin', {

511

method: 'POST',

512

headers: {'Content-Type': 'application/json'},

513

});

514

const options = await optionsResponse.json();

515

516

// Convert challenge to ArrayBuffer

517

options.challenge = base64ToArrayBuffer(options.challenge);

518

519

// Get assertion

520

const assertion = await navigator.credentials.get({

521

publicKey: options

522

});

523

524

// Send assertion to server

525

const authResponse = await fetch('/webauthn/authenticate/complete', {

526

method: 'POST',

527

headers: {'Content-Type': 'application/json'},

528

body: JSON.stringify({

529

id: assertion.id,

530

rawId: arrayBufferToBase64(assertion.rawId),

531

response: {

532

clientDataJSON: arrayBufferToBase64(assertion.response.clientDataJSON),

533

authenticatorData: arrayBufferToBase64(assertion.response.authenticatorData),

534

signature: arrayBufferToBase64(assertion.response.signature),

535

userHandle: assertion.response.userHandle ?

536

arrayBufferToBase64(assertion.response.userHandle) : null

537

}

538

})

539

});

540

541

const result = await authResponse.json();

542

if (result.success) {

543

window.location.href = result.redirect || '/dashboard';

544

} else {

545

alert('Authentication failed: ' + result.error);

546

}

547

548

} catch (error) {

549

console.error('WebAuthn authentication error:', error);

550

alert('WebAuthn authentication failed');

551

}

552

}

553

554

// Utility functions for base64/ArrayBuffer conversion

555

function base64ToArrayBuffer(base64) {

556

const binaryString = atob(base64);

557

const bytes = new Uint8Array(binaryString.length);

558

for (let i = 0; i < binaryString.length; i++) {

559

bytes[i] = binaryString.charCodeAt(i);

560

}

561

return bytes.buffer;

562

}

563

564

function arrayBufferToBase64(buffer) {

565

const bytes = new Uint8Array(buffer);

566

let binary = '';

567

for (let i = 0; i < bytes.byteLength; i++) {

568

binary += String.fromCharCode(bytes[i]);

569

}

570

return btoa(binary);

571

}

572

```

573

574

## Security Considerations

575

576

### Relying Party Configuration

577

- Set `SECURITY_WEBAUTHN_RP_ID` to your domain name for proper origin validation

578

- Use HTTPS in production - WebAuthn requires secure contexts

579

- Configure appropriate `SECURITY_WEBAUTHN_RP_NAME` for user recognition

580

581

### Credential Storage

582

- Store credential public keys securely in your database

583

- Never store private keys - they remain on the authenticator device

584

- Implement proper credential ID uniqueness constraints

585

586

### Challenge Management

587

- Use cryptographically secure random challenges

588

- Implement appropriate challenge timeouts (default: 5 minutes)

589

- Store challenges server-side to prevent replay attacks

590

591

### User Verification

592

- Configure appropriate user verification requirements based on security needs

593

- Consider authenticator attachment preferences for your use case

594

- Implement fallback authentication methods for accessibility

595

596

## Browser Support

597

598

WebAuthn is supported in modern browsers:

599

- Chrome 67+

600

- Firefox 60+

601

- Safari 14+

602

- Edge 18+

603

604

Consider implementing feature detection and fallback authentication methods for broader compatibility.