or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

common-utilities.mddjango-integration.mdflask-integration.mdhttp-clients.mdindex.mdjose.mdoauth1.mdoauth2.mdoidc.md

oauth2.mddocs/

0

# OAuth 2.0 Implementation

1

2

Comprehensive OAuth 2.0 implementation following RFC 6749 and related RFCs. Supports all standard grant types: authorization code, implicit, resource owner password credentials, client credentials, and refresh token. Includes advanced features like PKCE (RFC 7636), device flow (RFC 8628), and bearer tokens (RFC 6750).

3

4

## Capabilities

5

6

### OAuth 2.0 Client

7

8

High-level OAuth 2.0 client implementation for performing authorization flows with automatic token management.

9

10

```python { .api }

11

class OAuth2Client:

12

"""OAuth 2.0 client implementation."""

13

14

def __init__(self, client_id: str, client_secret: str = None, token_endpoint_auth_method: str = 'client_secret_basic', revocation_endpoint_auth_method: str = None, scope: str = None, redirect_uri: str = None, token: dict = None, token_placement: str = 'header', update_token: callable = None, **kwargs) -> None:

15

"""

16

Initialize OAuth 2.0 client.

17

18

Args:

19

client_id: Client identifier

20

client_secret: Client secret

21

token_endpoint_auth_method: Token endpoint authentication method

22

revocation_endpoint_auth_method: Revocation endpoint authentication method

23

scope: Default scope

24

redirect_uri: Default redirect URI

25

token: Access token dictionary

26

token_placement: Where to place token (header, body, uri)

27

update_token: Callback for token updates

28

"""

29

30

def create_authorization_url(self, authorization_endpoint: str, state: str = None, code_challenge: str = None, code_challenge_method: str = None, **kwargs) -> tuple:

31

"""

32

Create authorization URL for authorization code flow.

33

34

Args:

35

authorization_endpoint: Authorization server's authorization endpoint

36

state: CSRF protection state parameter

37

code_challenge: PKCE code challenge

38

code_challenge_method: PKCE code challenge method

39

**kwargs: Additional authorization parameters

40

41

Returns:

42

Tuple of (authorization_url, state)

43

"""

44

45

def fetch_token(self, token_endpoint: str, code: str = None, authorization_response: str = None, body: str = '', auth: tuple = None, username: str = None, password: str = None, **kwargs) -> dict:

46

"""

47

Fetch access token from authorization server.

48

49

Args:

50

token_endpoint: Token endpoint URL

51

code: Authorization code

52

authorization_response: Full authorization response URL

53

body: Additional request body

54

auth: HTTP basic auth tuple

55

username: Username for password grant

56

password: Password for password grant

57

**kwargs: Additional token parameters

58

59

Returns:

60

Token dictionary with access_token, token_type, etc.

61

"""

62

63

def refresh_token(self, token_endpoint: str, refresh_token: str = None, body: str = '', auth: tuple = None, **kwargs) -> dict:

64

"""

65

Refresh access token using refresh token.

66

67

Args:

68

token_endpoint: Token endpoint URL

69

refresh_token: Refresh token (uses stored token if None)

70

body: Additional request body

71

auth: HTTP basic auth tuple

72

**kwargs: Additional parameters

73

74

Returns:

75

New token dictionary

76

"""

77

78

def revoke_token(self, revocation_endpoint: str, token: str = None, token_type_hint: str = None, body: str = '', auth: tuple = None, **kwargs) -> dict:

79

"""

80

Revoke access or refresh token.

81

82

Args:

83

revocation_endpoint: Revocation endpoint URL

84

token: Token to revoke (uses stored token if None)

85

token_type_hint: Hint about token type

86

body: Additional request body

87

auth: HTTP basic auth tuple

88

**kwargs: Additional parameters

89

90

Returns:

91

Response dictionary

92

"""

93

94

def introspect_token(self, introspection_endpoint: str, token: str = None, token_type_hint: str = None, **kwargs) -> dict:

95

"""

96

Introspect token at authorization server.

97

98

Args:

99

introspection_endpoint: Introspection endpoint URL

100

token: Token to introspect

101

token_type_hint: Hint about token type

102

**kwargs: Additional parameters

103

104

Returns:

105

Introspection response dictionary

106

"""

107

108

def register_client(self, registration_endpoint: str, **kwargs) -> dict:

109

"""

110

Register client dynamically.

111

112

Args:

113

registration_endpoint: Registration endpoint URL

114

**kwargs: Client metadata

115

116

Returns:

117

Registration response with client credentials

118

"""

119

120

@property

121

def access_token(self) -> str:

122

"""Get current access token."""

123

124

@property

125

def token_type(self) -> str:

126

"""Get current token type."""

127

```

128

129

### OAuth 2.0 Server Components

130

131

Server-side components for implementing OAuth 2.0 authorization servers.

132

133

```python { .api }

134

class AuthorizationServer:

135

"""OAuth 2.0 authorization server."""

136

137

def __init__(self, query_client: callable, save_token: callable = None) -> None:

138

"""

139

Initialize authorization server.

140

141

Args:

142

query_client: Function to query client by client_id

143

save_token: Function to save issued tokens

144

"""

145

146

def register_grant(self, grant_cls: type, extensions: list = None) -> None:

147

"""

148

Register a grant type.

149

150

Args:

151

grant_cls: Grant class to register

152

extensions: List of grant extensions

153

"""

154

155

def register_endpoint(self, endpoint: object) -> None:

156

"""

157

Register an endpoint.

158

159

Args:

160

endpoint: Endpoint instance

161

"""

162

163

def validate_consent_request(self, request: OAuth2Request, end_user: object = None) -> None:

164

"""

165

Validate consent request at authorization endpoint.

166

167

Args:

168

request: OAuth2Request object

169

end_user: End user object

170

"""

171

172

def create_authorization_response(self, request: OAuth2Request, grant_user: callable) -> tuple:

173

"""

174

Create authorization response.

175

176

Args:

177

request: OAuth2Request object

178

grant_user: Function to grant authorization to user

179

180

Returns:

181

Tuple of (status_code, body, headers)

182

"""

183

184

def create_token_response(self, request: OAuth2Request) -> tuple:

185

"""

186

Create token response.

187

188

Args:

189

request: OAuth2Request object

190

191

Returns:

192

Tuple of (status_code, body, headers)

193

"""

194

195

def create_revocation_response(self, request: OAuth2Request) -> tuple:

196

"""

197

Create revocation response.

198

199

Args:

200

request: OAuth2Request object

201

202

Returns:

203

Tuple of (status_code, body, headers)

204

"""

205

206

class ResourceProtector:

207

"""OAuth 2.0 resource server protection."""

208

209

def __init__(self) -> None:

210

"""Initialize resource protector."""

211

212

def register_token_validator(self, validator: 'TokenValidator') -> None:

213

"""

214

Register token validator.

215

216

Args:

217

validator: Token validator instance

218

"""

219

220

def validate_request(self, scopes: list, request: OAuth2Request) -> 'OAuth2Token':

221

"""

222

Validate OAuth 2.0 request.

223

224

Args:

225

scopes: Required scopes

226

request: OAuth2Request object

227

228

Returns:

229

OAuth2Token object if valid

230

"""

231

232

def acquire_token(self, scopes: list = None, request: OAuth2Request = None) -> 'OAuth2Token':

233

"""

234

Acquire token from request.

235

236

Args:

237

scopes: Required scopes

238

request: OAuth2Request object

239

240

Returns:

241

OAuth2Token object

242

"""

243

```

244

245

### Request and Token Objects

246

247

Core objects for representing OAuth 2.0 requests and tokens.

248

249

```python { .api }

250

class OAuth2Request:

251

"""OAuth 2.0 request wrapper."""

252

253

def __init__(self, method: str, uri: str, body: str = None, headers: dict = None) -> None:

254

"""

255

Initialize OAuth 2.0 request.

256

257

Args:

258

method: HTTP method

259

uri: Request URI

260

body: Request body

261

headers: Request headers

262

"""

263

264

@property

265

def client_id(self) -> str:

266

"""Get client ID from request."""

267

268

@property

269

def client_secret(self) -> str:

270

"""Get client secret from request."""

271

272

@property

273

def redirect_uri(self) -> str:

274

"""Get redirect URI from request."""

275

276

@property

277

def scope(self) -> str:

278

"""Get scope from request."""

279

280

@property

281

def state(self) -> str:

282

"""Get state from request."""

283

284

@property

285

def response_type(self) -> str:

286

"""Get response type from request."""

287

288

@property

289

def grant_type(self) -> str:

290

"""Get grant type from request."""

291

292

class OAuth2Token:

293

"""OAuth 2.0 token representation."""

294

295

def __init__(self, params: dict = None) -> None:

296

"""

297

Initialize OAuth 2.0 token.

298

299

Args:

300

params: Token parameters dictionary

301

"""

302

303

@property

304

def access_token(self) -> str:

305

"""Get access token."""

306

307

@property

308

def token_type(self) -> str:

309

"""Get token type."""

310

311

@property

312

def refresh_token(self) -> str:

313

"""Get refresh token."""

314

315

@property

316

def expires_in(self) -> int:

317

"""Get token expiration time."""

318

319

@property

320

def scope(self) -> str:

321

"""Get token scope."""

322

323

def is_expired(self) -> bool:

324

"""Check if token is expired."""

325

326

def get_expires_at(self) -> int:

327

"""Get expiration timestamp."""

328

```

329

330

### Grant Types

331

332

Implementation of standard OAuth 2.0 grant types.

333

334

```python { .api }

335

class BaseGrant:

336

"""Base class for OAuth 2.0 grants."""

337

338

def __init__(self, request: OAuth2Request, server: AuthorizationServer) -> None:

339

"""

340

Initialize grant.

341

342

Args:

343

request: OAuth2Request object

344

server: AuthorizationServer instance

345

"""

346

347

def validate_request(self) -> None:

348

"""Validate the grant request."""

349

350

def create_authorization_response(self, redirect_uri: str, grant_user: callable) -> dict:

351

"""Create authorization response."""

352

353

def create_token_response(self) -> dict:

354

"""Create token response."""

355

356

class AuthorizationCodeGrant(BaseGrant):

357

"""Authorization code grant implementation."""

358

359

GRANT_TYPE: str = 'authorization_code'

360

RESPONSE_TYPES: list = ['code']

361

362

def create_authorization_code(self, client: object, grant_user: callable, request: OAuth2Request) -> str:

363

"""

364

Create authorization code.

365

366

Args:

367

client: Client object

368

grant_user: Grant user function

369

request: OAuth2Request object

370

371

Returns:

372

Authorization code string

373

"""

374

375

def parse_authorization_code(self, code: str, client: object) -> object:

376

"""

377

Parse and validate authorization code.

378

379

Args:

380

code: Authorization code

381

client: Client object

382

383

Returns:

384

Authorization code object

385

"""

386

387

def delete_authorization_code(self, authorization_code: object) -> None:

388

"""

389

Delete authorization code after use.

390

391

Args:

392

authorization_code: Authorization code object

393

"""

394

395

def authenticate_user(self, authorization_code: object) -> object:

396

"""

397

Authenticate user from authorization code.

398

399

Args:

400

authorization_code: Authorization code object

401

402

Returns:

403

User object

404

"""

405

406

class ImplicitGrant(BaseGrant):

407

"""Implicit grant implementation."""

408

409

GRANT_TYPE: str = None

410

RESPONSE_TYPES: list = ['token']

411

412

class ResourceOwnerPasswordCredentialsGrant(BaseGrant):

413

"""Resource owner password credentials grant implementation."""

414

415

GRANT_TYPE: str = 'password'

416

417

def authenticate_user(self, username: str, password: str, client: object, request: OAuth2Request) -> object:

418

"""

419

Authenticate user with username and password.

420

421

Args:

422

username: Username

423

password: Password

424

client: Client object

425

request: OAuth2Request object

426

427

Returns:

428

User object if authenticated

429

"""

430

431

class ClientCredentialsGrant(BaseGrant):

432

"""Client credentials grant implementation."""

433

434

GRANT_TYPE: str = 'client_credentials'

435

436

class RefreshTokenGrant(BaseGrant):

437

"""Refresh token grant implementation."""

438

439

GRANT_TYPE: str = 'refresh_token'

440

441

def authenticate_refresh_token(self, refresh_token: str, client: object, request: OAuth2Request) -> object:

442

"""

443

Authenticate refresh token.

444

445

Args:

446

refresh_token: Refresh token string

447

client: Client object

448

request: OAuth2Request object

449

450

Returns:

451

Token object if valid

452

"""

453

454

def authenticate_user(self, credential: object) -> object:

455

"""

456

Authenticate user from refresh token credential.

457

458

Args:

459

credential: Token credential object

460

461

Returns:

462

User object

463

"""

464

465

def revoke_old_credential(self, credential: object) -> None:

466

"""

467

Revoke old refresh token.

468

469

Args:

470

credential: Token credential to revoke

471

"""

472

```

473

474

### Bearer Token Support

475

476

RFC 6750 Bearer Token implementation.

477

478

```python { .api }

479

class BearerTokenGenerator:

480

"""Generate bearer tokens."""

481

482

def __init__(self, access_token_generator: callable = None, refresh_token_generator: callable = None, expires_generator: callable = None) -> None:

483

"""

484

Initialize bearer token generator.

485

486

Args:

487

access_token_generator: Function to generate access tokens

488

refresh_token_generator: Function to generate refresh tokens

489

expires_generator: Function to generate expiration times

490

"""

491

492

def generate(self, client: object, grant_type: str, user: object = None, scope: str = None, expires_in: int = None, include_refresh_token: bool = True) -> dict:

493

"""

494

Generate bearer token.

495

496

Args:

497

client: Client object

498

grant_type: Grant type used

499

user: User object

500

scope: Token scope

501

expires_in: Token lifetime in seconds

502

include_refresh_token: Whether to include refresh token

503

504

Returns:

505

Token dictionary

506

"""

507

508

class BearerTokenValidator:

509

"""Validate bearer tokens."""

510

511

def authenticate_token(self, token_string: str) -> object:

512

"""

513

Authenticate bearer token.

514

515

Args:

516

token_string: Bearer token string

517

518

Returns:

519

Token object if valid

520

"""

521

522

def request_invalid(self, request: OAuth2Request) -> bool:

523

"""

524

Check if request is invalid.

525

526

Args:

527

request: OAuth2Request object

528

529

Returns:

530

True if request is invalid

531

"""

532

533

def token_revoked(self, token: object) -> bool:

534

"""

535

Check if token is revoked.

536

537

Args:

538

token: Token object

539

540

Returns:

541

True if token is revoked

542

"""

543

544

def add_bearer_token(uri: str, http_method: str, body: str, headers: dict, token: dict) -> tuple:

545

"""

546

Add bearer token to request.

547

548

Args:

549

uri: Request URI

550

http_method: HTTP method

551

body: Request body

552

headers: Request headers

553

token: Token dictionary

554

555

Returns:

556

Tuple of (uri, headers, body)

557

"""

558

```

559

560

### PKCE Support

561

562

RFC 7636 Proof Key for Code Exchange implementation.

563

564

```python { .api }

565

class CodeChallenge:

566

"""PKCE code challenge implementation."""

567

568

def __init__(self, code_verifier: str, code_challenge_method: str = 'S256') -> None:

569

"""

570

Initialize code challenge.

571

572

Args:

573

code_verifier: Code verifier string

574

code_challenge_method: Challenge method (plain or S256)

575

"""

576

577

def get_code_challenge(self) -> str:

578

"""Get code challenge string."""

579

580

def get_code_challenge_method(self) -> str:

581

"""Get code challenge method."""

582

583

def verify_code_verifier(self, code_verifier: str) -> bool:

584

"""

585

Verify code verifier against challenge.

586

587

Args:

588

code_verifier: Code verifier to verify

589

590

Returns:

591

True if verifier is valid

592

"""

593

594

def create_s256_code_challenge(code_verifier: str) -> str:

595

"""

596

Create S256 code challenge from verifier.

597

598

Args:

599

code_verifier: Code verifier string

600

601

Returns:

602

S256 code challenge string

603

"""

604

```

605

606

### Device Authorization Flow

607

608

RFC 8628 Device Authorization Grant implementation.

609

610

```python { .api }

611

class DeviceAuthorizationEndpoint:

612

"""Device authorization endpoint."""

613

614

def __init__(self, server: AuthorizationServer) -> None:

615

"""

616

Initialize device authorization endpoint.

617

618

Args:

619

server: AuthorizationServer instance

620

"""

621

622

def create_endpoint_response(self, request: OAuth2Request) -> tuple:

623

"""

624

Create device authorization response.

625

626

Args:

627

request: OAuth2Request object

628

629

Returns:

630

Tuple of (status_code, body, headers)

631

"""

632

633

class DeviceCodeGrant(BaseGrant):

634

"""Device code grant implementation."""

635

636

GRANT_TYPE: str = 'urn:ietf:params:oauth:grant-type:device_code'

637

638

def query_device_credential(self, device_code: str, client: object) -> object:

639

"""

640

Query device credential by device code.

641

642

Args:

643

device_code: Device code

644

client: Client object

645

646

Returns:

647

Device credential object

648

"""

649

650

def should_slow_down(self, credential: object, now: int) -> bool:

651

"""

652

Check if client should slow down polling.

653

654

Args:

655

credential: Device credential object

656

now: Current timestamp

657

658

Returns:

659

True if client should slow down

660

"""

661

662

# Device flow constants

663

DEVICE_CODE_GRANT_TYPE: str = 'urn:ietf:params:oauth:grant-type:device_code'

664

665

# Device flow errors

666

class AuthorizationPendingError(OAuth2Error):

667

"""Authorization is pending user action."""

668

error: str = 'authorization_pending'

669

670

class SlowDownError(OAuth2Error):

671

"""Client is polling too frequently."""

672

error: str = 'slow_down'

673

```

674

675

### Model Mixins

676

677

Mixins for database models to store OAuth 2.0 data.

678

679

```python { .api }

680

class ClientMixin:

681

"""Mixin for OAuth 2.0 client model."""

682

683

client_id: str # Client identifier

684

client_secret: str # Client secret (may be None for public clients)

685

client_id_issued_at: int # Client ID issued timestamp

686

client_secret_expires_at: int # Client secret expiration timestamp

687

688

def get_client_id(self) -> str:

689

"""Get client ID."""

690

691

def get_default_redirect_uri(self) -> str:

692

"""Get default redirect URI."""

693

694

def get_allowed_scope(self, scope: str) -> str:

695

"""Get allowed scope for client."""

696

697

def check_redirect_uri(self, redirect_uri: str) -> bool:

698

"""Check if redirect URI is allowed."""

699

700

def has_client_secret(self) -> bool:

701

"""Check if client has a secret."""

702

703

def check_client_secret(self, client_secret: str) -> bool:

704

"""Verify client secret."""

705

706

def check_token_endpoint_auth_method(self, method: str) -> bool:

707

"""Check if token endpoint auth method is supported."""

708

709

def check_response_type(self, response_type: str) -> bool:

710

"""Check if response type is supported."""

711

712

def check_grant_type(self, grant_type: str) -> bool:

713

"""Check if grant type is supported."""

714

715

class AuthorizationCodeMixin:

716

"""Mixin for authorization code model."""

717

718

code: str # Authorization code

719

client_id: str # Client identifier

720

redirect_uri: str # Redirect URI

721

scope: str # Authorized scope

722

user_id: str # User identifier

723

code_challenge: str # PKCE code challenge

724

code_challenge_method: str # PKCE challenge method

725

726

def is_expired(self) -> bool:

727

"""Check if authorization code is expired."""

728

729

def get_redirect_uri(self) -> str:

730

"""Get redirect URI."""

731

732

def get_scope(self) -> str:

733

"""Get authorized scope."""

734

735

def get_user_id(self) -> str:

736

"""Get user ID."""

737

738

def get_code_challenge(self) -> str:

739

"""Get PKCE code challenge."""

740

741

def get_code_challenge_method(self) -> str:

742

"""Get PKCE challenge method."""

743

744

class TokenMixin:

745

"""Mixin for access token model."""

746

747

access_token: str # Access token

748

client_id: str # Client identifier

749

token_type: str # Token type (usually 'Bearer')

750

refresh_token: str # Refresh token

751

scope: str # Token scope

752

user_id: str # User identifier

753

issued_at: int # Token issued timestamp

754

expires_in: int # Token lifetime in seconds

755

756

def get_scope(self) -> str:

757

"""Get token scope."""

758

759

def get_user_id(self) -> str:

760

"""Get user ID."""

761

762

def is_expired(self) -> bool:

763

"""Check if token is expired."""

764

765

def is_revoked(self) -> bool:

766

"""Check if token is revoked."""

767

```

768

769

### Utility Functions

770

771

Helper functions for scope and parameter handling.

772

773

```python { .api }

774

def scope_to_list(scope: str) -> list:

775

"""

776

Convert scope string to list.

777

778

Args:

779

scope: Space-separated scope string

780

781

Returns:

782

List of scope values

783

"""

784

785

def list_to_scope(scopes: list) -> str:

786

"""

787

Convert scope list to string.

788

789

Args:

790

scopes: List of scope values

791

792

Returns:

793

Space-separated scope string

794

"""

795

```

796

797

## Usage Examples

798

799

### Authorization Code Flow

800

801

```python

802

from authlib.oauth2 import OAuth2Client

803

804

# Initialize client

805

client = OAuth2Client(

806

client_id='your-client-id',

807

client_secret='your-client-secret',

808

scope='read write'

809

)

810

811

# Step 1: Generate authorization URL

812

authorization_url, state = client.create_authorization_url(

813

'https://provider.com/authorize',

814

state='random-state-string'

815

)

816

print(f"Visit: {authorization_url}")

817

818

# Step 2: Exchange code for token

819

token = client.fetch_token(

820

'https://provider.com/token',

821

code='authorization-code-from-callback'

822

)

823

print(f"Access token: {token['access_token']}")

824

825

# Step 3: Refresh token when needed

826

if 'refresh_token' in token:

827

new_token = client.refresh_token(

828

'https://provider.com/token',

829

refresh_token=token['refresh_token']

830

)

831

```

832

833

### PKCE Flow

834

835

```python

836

from authlib.oauth2 import OAuth2Client, create_s256_code_challenge

837

from authlib.common.security import generate_token

838

839

# Generate PKCE parameters

840

code_verifier = generate_token(128)

841

code_challenge = create_s256_code_challenge(code_verifier)

842

843

# Create authorization URL with PKCE

844

client = OAuth2Client(client_id='public-client-id')

845

auth_url, state = client.create_authorization_url(

846

'https://provider.com/authorize',

847

code_challenge=code_challenge,

848

code_challenge_method='S256'

849

)

850

851

# Exchange code with verifier

852

token = client.fetch_token(

853

'https://provider.com/token',

854

code='authorization-code',

855

code_verifier=code_verifier

856

)

857

```

858

859

### Server Implementation

860

861

```python

862

from authlib.oauth2 import AuthorizationServer, ResourceProtector

863

from authlib.oauth2.rfc6749.grants import AuthorizationCodeGrant

864

865

# Define query functions

866

def query_client(client_id):

867

return get_client_by_id(client_id)

868

869

def save_token(token, request, *args, **kwargs):

870

store_access_token(token)

871

872

# Create authorization server

873

authorization_server = AuthorizationServer(

874

query_client=query_client,

875

save_token=save_token

876

)

877

878

# Register authorization code grant

879

authorization_server.register_grant(AuthorizationCodeGrant)

880

881

# Handle authorization endpoint

882

@app.route('/authorize', methods=['GET', 'POST'])

883

def authorize():

884

request = OAuth2Request(request.method, request.url, request.form, request.headers)

885

886

if request.method == 'GET':

887

# Show authorization form

888

try:

889

authorization_server.validate_consent_request(request, current_user)

890

return render_template('authorize.html')

891

except OAuth2Error as error:

892

return {'error': error.error}, 400

893

894

# Handle authorization

895

def grant_user():

896

return current_user

897

898

status_code, body, headers = authorization_server.create_authorization_response(

899

request, grant_user

900

)

901

return Response(body, status=status_code, headers=headers)

902

903

# Handle token endpoint

904

@app.route('/token', methods=['POST'])

905

def issue_token():

906

request = OAuth2Request(request.method, request.url, request.form, request.headers)

907

status_code, body, headers = authorization_server.create_token_response(request)

908

return Response(body, status=status_code, headers=headers)

909

```