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
```