or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-routing.mdauthentication.mdcss-styling.mddevelopment-tools.mdform-handling.mdhtml-components.mdhtmx-integration.mdindex.mdjavascript-integration.mdnotifications.mdsvg-components.md

authentication.mddocs/

0

# Authentication and Security

1

2

OAuth integration with multiple providers, basic authentication middleware, and session management for secure web applications.

3

4

## Capabilities

5

6

### OAuth Integration

7

8

Complete OAuth authentication system with support for multiple providers including Google, GitHub, Discord, Hugging Face, and Auth0.

9

10

```python { .api }

11

class OAuth:

12

"""

13

Main OAuth handler class.

14

15

Manages OAuth authentication flow including authorization,

16

token exchange, and user information retrieval.

17

"""

18

19

def __init__(self, app, client, redirect_uri: str, logout_uri: str = '/logout'):

20

"""

21

Initialize OAuth handler.

22

23

Args:

24

app: FastHTML application instance

25

client: OAuth client (GoogleAppClient, GitHubAppClient, etc.)

26

redirect_uri: URI to redirect after authentication

27

logout_uri: URI for logout functionality

28

"""

29

30

def login_link(self, info: str = None, **scope) -> str:

31

"""Generate OAuth login URL."""

32

33

def logout_link(self) -> str:

34

"""Generate OAuth logout URL."""

35

```

36

37

### OAuth Provider Clients

38

39

Pre-configured OAuth clients for popular authentication providers.

40

41

```python { .api }

42

class GoogleAppClient:

43

"""

44

Google OAuth integration.

45

46

Provides authentication using Google OAuth 2.0 service.

47

"""

48

49

def __init__(self, client_id: str, client_secret: str, code: str, scope: list, project_id: str = None, **kwargs):

50

"""

51

Initialize Google OAuth client.

52

53

Args:

54

client_id: Google OAuth client ID

55

client_secret: Google OAuth client secret

56

code: Authorization code from OAuth flow

57

scope: OAuth scopes to request

58

project_id: Google Cloud project ID

59

**kwargs: Additional OAuth parameters

60

"""

61

62

@classmethod

63

def from_file(cls, fname: str, code: str, scope: list, **kwargs):

64

"""Create client from credentials file."""

65

66

def consent_url(self, proj: str) -> str:

67

"""Get consent screen URL."""

68

69

def creds(self):

70

"""Create Google credentials object."""

71

72

class GitHubAppClient:

73

"""

74

GitHub OAuth integration.

75

76

Provides authentication using GitHub OAuth service.

77

"""

78

79

def __init__(self, client_id: str, client_secret: str, code: str, scope: list, **kwargs):

80

"""

81

Initialize GitHub OAuth client.

82

83

Args:

84

client_id: GitHub OAuth client ID

85

client_secret: GitHub OAuth client secret

86

code: Authorization code from OAuth flow

87

scope: OAuth scopes to request

88

**kwargs: Additional OAuth parameters

89

"""

90

91

class HuggingFaceClient:

92

"""

93

Hugging Face OAuth integration.

94

95

Provides authentication using Hugging Face OAuth service.

96

"""

97

98

def __init__(self, client_id: str, client_secret: str, code: str, scope: list, state: str, **kwargs):

99

"""

100

Initialize Hugging Face OAuth client.

101

102

Args:

103

client_id: Hugging Face OAuth client ID

104

client_secret: Hugging Face OAuth client secret

105

code: Authorization code from OAuth flow

106

scope: OAuth scopes to request

107

state: State parameter for CSRF protection

108

**kwargs: Additional OAuth parameters

109

"""

110

111

class DiscordAppClient:

112

"""

113

Discord OAuth integration.

114

115

Provides authentication using Discord OAuth service.

116

"""

117

118

def __init__(self, client_id: str, client_secret: str, is_user: bool = False, perms: int = 0, scope: list = None, **kwargs):

119

"""

120

Initialize Discord OAuth client.

121

122

Args:

123

client_id: Discord OAuth client ID

124

client_secret: Discord OAuth client secret

125

is_user: Whether to use user authentication

126

perms: Permission bitfield

127

scope: OAuth scopes to request

128

**kwargs: Additional OAuth parameters

129

"""

130

131

def login_link(self, redirect_uri: str, scope: list = None, state: str = None) -> str:

132

"""Get Discord login URL."""

133

134

def parse_response(self, code: str, redirect_uri: str):

135

"""Parse OAuth callback response."""

136

137

class Auth0AppClient:

138

"""

139

Auth0 OAuth integration.

140

141

Provides authentication using Auth0 service.

142

"""

143

144

def __init__(self, domain: str, client_id: str, client_secret: str, code: str, scope: list, redirect_uri: str = "", **kwargs):

145

"""

146

Initialize Auth0 OAuth client.

147

148

Args:

149

domain: Auth0 domain

150

client_id: Auth0 client ID

151

client_secret: Auth0 client secret

152

code: Authorization code from OAuth flow

153

scope: OAuth scopes to request

154

redirect_uri: OAuth redirect URI

155

**kwargs: Additional OAuth parameters

156

"""

157

158

def login_link(self, req) -> str:

159

"""Get Auth0 login link for request."""

160

```

161

162

### Basic Authentication Middleware

163

164

HTTP Basic authentication middleware for simple username/password authentication.

165

166

```python { .api }

167

class BasicAuthMiddleware:

168

"""

169

HTTP Basic authentication middleware.

170

171

Provides simple username/password authentication using

172

HTTP Basic authentication standard.

173

"""

174

175

def __init__(self, app, verifier, realm: str = 'Secure Area'):

176

"""

177

Initialize basic auth middleware.

178

179

Args:

180

app: ASGI application

181

verifier: Function to verify username/password

182

realm: Authentication realm name

183

"""

184

185

def user_pwd_auth(user: str, pwd: str) -> bool:

186

"""

187

Username/password authentication function.

188

189

Verify user credentials against stored values.

190

191

Args:

192

user: Username to verify

193

pwd: Password to verify

194

195

Returns:

196

bool: True if credentials are valid

197

"""

198

199

def basic_logout() -> str:

200

"""

201

Generate logout URL for basic authentication.

202

203

Returns:

204

str: Logout URL that clears basic auth credentials

205

"""

206

```

207

208

### OAuth Utility Functions

209

210

Helper functions for OAuth flow management and user information handling.

211

212

```python { .api }

213

def login_link(client, redirect_uri: str, **scope) -> str:

214

"""

215

Generate OAuth login URL.

216

217

Args:

218

client: OAuth client instance

219

redirect_uri: Redirect URI after authentication

220

**scope: OAuth scope parameters

221

222

Returns:

223

str: OAuth authorization URL

224

"""

225

226

def get_host(request) -> str:

227

"""

228

Extract host from request.

229

230

Args:

231

request: HTTP request object

232

233

Returns:

234

str: Host name from request headers

235

"""

236

237

def redir_url(request, redirect_uri: str) -> str:

238

"""

239

Build redirect URL from request.

240

241

Args:

242

request: HTTP request object

243

redirect_uri: Base redirect URI

244

245

Returns:

246

str: Complete redirect URL

247

"""

248

249

def parse_response(code: str, state: str, client) -> dict:

250

"""

251

Parse OAuth callback response.

252

253

Args:

254

code: Authorization code from OAuth provider

255

state: State parameter for CSRF protection

256

client: OAuth client instance

257

258

Returns:

259

dict: Parsed response with tokens and user info

260

"""

261

262

def get_info(client, token: str) -> dict:

263

"""

264

Get user info from OAuth provider.

265

266

Args:

267

client: OAuth client instance

268

token: Access token

269

270

Returns:

271

dict: User information from provider

272

"""

273

274

def retr_info(code: str, client) -> dict:

275

"""

276

Retrieve and parse user info from OAuth response.

277

278

Args:

279

code: Authorization code

280

client: OAuth client instance

281

282

Returns:

283

dict: Complete user information

284

"""

285

286

def retr_id(code: str, client) -> str:

287

"""

288

Retrieve user ID from OAuth response.

289

290

Args:

291

code: Authorization code

292

client: OAuth client instance

293

294

Returns:

295

str: User ID from OAuth provider

296

"""

297

298

def url_match(url: str, pattern: str) -> bool:

299

"""

300

Match URL patterns for OAuth callback handling.

301

302

Args:

303

url: URL to match

304

pattern: Pattern to match against

305

306

Returns:

307

bool: True if URL matches pattern

308

"""

309

310

def load_creds(filename: str) -> dict:

311

"""

312

Load saved OAuth credentials from file.

313

314

Args:

315

filename: File containing saved credentials

316

317

Returns:

318

dict: Loaded credentials

319

"""

320

```

321

322

## Usage Examples

323

324

### Google OAuth Authentication

325

326

```python

327

from fasthtml.common import *

328

import os

329

330

# Set up OAuth credentials (use environment variables in production)

331

GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID')

332

GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET')

333

334

app, rt = fast_app(secret_key='your-secret-key')

335

336

# Create Google OAuth client

337

google_client = GoogleAppClient(

338

client_id=GOOGLE_CLIENT_ID,

339

client_secret=GOOGLE_CLIENT_SECRET,

340

scope=['openid', 'email', 'profile']

341

)

342

343

# Set up OAuth handler

344

oauth = OAuth(

345

app=app,

346

client=google_client,

347

redirect_uri='/auth/callback',

348

logout_uri='/logout'

349

)

350

351

@rt('/')

352

def homepage(request):

353

user = request.session.get('user')

354

if user:

355

return Titled("Welcome",

356

Div(

357

H1(f"Welcome, {user.get('name', 'User')}!"),

358

P(f"Email: {user.get('email', 'Not provided')}"),

359

Img(src=user.get('picture', ''), alt="Profile picture", width="100"),

360

A("Logout", href="/logout", cls="button")

361

)

362

)

363

else:

364

return Titled("Login Required",

365

Div(

366

H1("Please log in"),

367

A("Login with Google", href="/auth/login", cls="button")

368

)

369

)

370

371

@rt('/auth/login')

372

def login(request):

373

# Generate login URL and redirect

374

login_url = oauth.login_link()

375

return Redirect(login_url)

376

377

@rt('/auth/callback')

378

def auth_callback(request):

379

# Handle OAuth callback

380

code = request.query_params.get('code')

381

382

if code:

383

try:

384

# Get user information

385

user_info = retr_info(code, google_client)

386

387

# Store user in session

388

request.session['user'] = user_info

389

390

return Redirect('/')

391

except Exception as e:

392

return Div(f"Authentication failed: {str(e)}")

393

else:

394

return Div("Authentication was cancelled")

395

396

@rt('/logout')

397

def logout(request):

398

# Clear session

399

request.session.clear()

400

return Redirect('/')

401

402

serve()

403

```

404

405

### GitHub OAuth with User Permissions

406

407

```python

408

from fasthtml.common import *

409

import os

410

411

app, rt = fast_app(secret_key='your-secret-key')

412

413

# GitHub OAuth client with repository access

414

github_client = GitHubAppClient(

415

client_id=os.getenv('GITHUB_CLIENT_ID'),

416

client_secret=os.getenv('GITHUB_CLIENT_SECRET'),

417

scope=['user:email', 'repo']

418

)

419

420

oauth = OAuth(app, github_client, '/auth/github/callback')

421

422

@rt('/')

423

def homepage(request):

424

user = request.session.get('user')

425

if user:

426

return Titled("GitHub Integration",

427

Div(

428

H1(f"Hello, {user.get('login', 'User')}!"),

429

P(f"GitHub Profile: {user.get('html_url', '')}"),

430

P(f"Repositories: {user.get('public_repos', 0)}"),

431

P(f"Followers: {user.get('followers', 0)}"),

432

A("View Repositories", href="/repos"),

433

Br(),

434

A("Logout", href="/logout")

435

)

436

)

437

else:

438

return Titled("GitHub Login",

439

A("Login with GitHub", href="/auth/github")

440

)

441

442

@rt('/auth/github')

443

def github_login():

444

return Redirect(oauth.login_link())

445

446

@rt('/auth/github/callback')

447

def github_callback(request):

448

code = request.query_params.get('code')

449

if code:

450

user_info = retr_info(code, github_client)

451

request.session['user'] = user_info

452

return Redirect('/')

453

return Redirect('/auth/github')

454

455

@rt('/repos')

456

def view_repos(request):

457

user = request.session.get('user')

458

if not user:

459

return Redirect('/')

460

461

# This would typically fetch repositories using the OAuth token

462

return Titled("Your Repositories",

463

Div(

464

H2("Your GitHub Repositories"),

465

P("Repository list would be displayed here using the GitHub API"),

466

A("Back to Home", href="/")

467

)

468

)

469

```

470

471

### Basic Authentication

472

473

```python

474

from fasthtml.common import *

475

476

# Simple user database (use proper database in production)

477

USERS = {

478

'admin': 'secret123',

479

'user': 'password456'

480

}

481

482

def verify_credentials(username: str, password: str) -> bool:

483

"""Verify username and password"""

484

return USERS.get(username) == password

485

486

app, rt = fast_app()

487

488

# Add basic auth middleware

489

basic_auth = BasicAuthMiddleware(

490

app=app,

491

verifier=verify_credentials,

492

realm='Admin Area'

493

)

494

495

@rt('/')

496

def homepage(request):

497

# This route will require basic authentication

498

user = getattr(request, 'auth_user', 'Unknown')

499

return Titled("Protected Area",

500

Div(

501

H1(f"Welcome, {user}!"),

502

P("This is a protected area requiring authentication."),

503

A("Logout", href=basic_logout())

504

)

505

)

506

507

@rt('/login')

508

def login_form():

509

return Titled("Login",

510

Form(

511

Div(

512

Label("Username:", for_="username"),

513

Input(type="text", name="username", required=True),

514

cls="form-group"

515

),

516

Div(

517

Label("Password:", for_="password"),

518

Input(type="password", name="password", required=True),

519

cls="form-group"

520

),

521

Button("Login", type="submit"),

522

method="post",

523

action="/authenticate"

524

)

525

)

526

527

serve()

528

```

529

530

### Multi-Provider OAuth

531

532

```python

533

from fasthtml.common import *

534

import os

535

536

app, rt = fast_app(secret_key='your-secret-key')

537

538

# Set up multiple OAuth providers

539

providers = {

540

'google': GoogleAppClient(

541

client_id=os.getenv('GOOGLE_CLIENT_ID'),

542

client_secret=os.getenv('GOOGLE_CLIENT_SECRET')

543

),

544

'github': GitHubAppClient(

545

client_id=os.getenv('GITHUB_CLIENT_ID'),

546

client_secret=os.getenv('GITHUB_CLIENT_SECRET')

547

),

548

'discord': DiscordAppClient(

549

client_id=os.getenv('DISCORD_CLIENT_ID'),

550

client_secret=os.getenv('DISCORD_CLIENT_SECRET')

551

)

552

}

553

554

@rt('/')

555

def homepage(request):

556

user = request.session.get('user')

557

provider = request.session.get('provider')

558

559

if user:

560

return Titled("Multi-Provider Login",

561

Div(

562

H1(f"Welcome from {provider.title()}!"),

563

P(f"Name: {user.get('name', user.get('login', 'User'))}"),

564

P(f"Email: {user.get('email', 'Not provided')}"),

565

A("Logout", href="/logout")

566

)

567

)

568

else:

569

return Titled("Choose Login Provider",

570

Div(

571

H1("Login Options"),

572

Div(

573

A("Login with Google", href="/auth/google", cls="btn btn-google"),

574

Br(), Br(),

575

A("Login with GitHub", href="/auth/github", cls="btn btn-github"),

576

Br(), Br(),

577

A("Login with Discord", href="/auth/discord", cls="btn btn-discord"),

578

cls="login-buttons"

579

)

580

)

581

)

582

583

@rt('/auth/{provider}')

584

def provider_login(provider: str):

585

if provider not in providers:

586

return Redirect('/')

587

588

client = providers[provider]

589

oauth = OAuth(app, client, f'/auth/{provider}/callback')

590

return Redirect(oauth.login_link())

591

592

@rt('/auth/{provider}/callback')

593

def provider_callback(provider: str, request):

594

if provider not in providers:

595

return Redirect('/')

596

597

code = request.query_params.get('code')

598

if code:

599

client = providers[provider]

600

user_info = retr_info(code, client)

601

602

request.session['user'] = user_info

603

request.session['provider'] = provider

604

605

return Redirect('/')

606

607

return Redirect('/')

608

609

@rt('/logout')

610

def logout(request):

611

request.session.clear()

612

return Redirect('/')

613

```

614

615

### Custom Authentication with Database

616

617

```python

618

from fasthtml.common import *

619

import hashlib

620

import secrets

621

622

app, rt = fast_app(db='auth.db', secret_key='your-secret-key')

623

624

# Create users table

625

users = app.db.users

626

if not users.exists():

627

users.create({

628

'id': int,

629

'username': str,

630

'email': str,

631

'password_hash': str,

632

'salt': str,

633

'created_at': str

634

}, pk='id')

635

636

def hash_password(password: str, salt: str = None) -> tuple[str, str]:

637

"""Hash password with salt"""

638

if salt is None:

639

salt = secrets.token_hex(16)

640

641

password_hash = hashlib.pbkdf2_hmac(

642

'sha256',

643

password.encode('utf-8'),

644

salt.encode('utf-8'),

645

100000

646

).hex()

647

648

return password_hash, salt

649

650

def verify_password(password: str, password_hash: str, salt: str) -> bool:

651

"""Verify password against hash"""

652

computed_hash, _ = hash_password(password, salt)

653

return computed_hash == password_hash

654

655

@rt('/')

656

def homepage(request):

657

user_id = request.session.get('user_id')

658

if user_id:

659

user = users[user_id]

660

return Titled("Welcome",

661

Div(

662

H1(f"Welcome, {user.username}!"),

663

P(f"Email: {user.email}"),

664

A("Logout", href="/logout")

665

)

666

)

667

else:

668

return Titled("Authentication Demo",

669

Div(

670

H1("Please log in or register"),

671

A("Login", href="/login", cls="button"),

672

" ",

673

A("Register", href="/register", cls="button")

674

)

675

)

676

677

@rt('/register')

678

def register_form():

679

return Titled("Register",

680

Form(

681

Div(

682

Label("Username:", for_="username"),

683

Input(type="text", name="username", required=True),

684

cls="form-group"

685

),

686

Div(

687

Label("Email:", for_="email"),

688

Input(type="email", name="email", required=True),

689

cls="form-group"

690

),

691

Div(

692

Label("Password:", for_="password"),

693

Input(type="password", name="password", required=True),

694

cls="form-group"

695

),

696

Button("Register", type="submit"),

697

method="post",

698

action="/register/submit"

699

)

700

)

701

702

@rt('/register/submit', methods=['POST'])

703

def register_user(username: str, email: str, password: str):

704

# Check if user exists

705

existing = users.select().where('username = ? OR email = ?', username, email).first()

706

if existing:

707

return Div("Username or email already exists", style="color: red;")

708

709

# Hash password and create user

710

password_hash, salt = hash_password(password)

711

712

user_id = users.insert({

713

'username': username,

714

'email': email,

715

'password_hash': password_hash,

716

'salt': salt,

717

'created_at': str(datetime.now())

718

}).last_pk

719

720

# Log user in

721

request.session['user_id'] = user_id

722

723

return Redirect('/')

724

725

@rt('/login')

726

def login_form():

727

return Titled("Login",

728

Form(

729

Div(

730

Label("Username:", for_="username"),

731

Input(type="text", name="username", required=True),

732

cls="form-group"

733

),

734

Div(

735

Label("Password:", for_="password"),

736

Input(type="password", name="password", required=True),

737

cls="form-group"

738

),

739

Button("Login", type="submit"),

740

method="post",

741

action="/login/submit"

742

)

743

)

744

745

@rt('/login/submit', methods=['POST'])

746

def authenticate_user(username: str, password: str, request):

747

user = users.select().where('username = ?', username).first()

748

749

if user and verify_password(password, user.password_hash, user.salt):

750

request.session['user_id'] = user.id

751

return Redirect('/')

752

else:

753

return Div("Invalid username or password", style="color: red;")

754

755

@rt('/logout')

756

def logout(request):

757

request.session.clear()

758

return Redirect('/')

759

```