0
# Flask Integration
1
2
Complete Flask integration providing OAuth client registry and OAuth 2.0 server implementations. Supports automatic token management, session integration, Flask-specific request/response handling, and seamless integration with Flask's application context and blueprints.
3
4
## Capabilities
5
6
### Flask OAuth Client
7
8
Flask-specific OAuth client registry with automatic token management and session integration.
9
10
```python { .api }
11
class OAuth:
12
"""Flask OAuth client registry."""
13
14
def __init__(self, app: Flask = None) -> None:
15
"""
16
Initialize Flask OAuth registry.
17
18
Args:
19
app: Flask application instance
20
"""
21
22
def init_app(self, app: Flask) -> None:
23
"""
24
Initialize OAuth registry with Flask app.
25
26
Args:
27
app: Flask application instance
28
"""
29
30
def register(self, name: str, client_id: str = None, client_secret: str = None, client_kwargs: dict = None, **kwargs) -> 'FlaskOAuth2App':
31
"""
32
Register an OAuth provider.
33
34
Args:
35
name: Provider name for reference
36
client_id: OAuth client ID
37
client_secret: OAuth client secret
38
client_kwargs: Additional client configuration
39
**kwargs: Provider configuration (server_metadata_url, etc.)
40
41
Returns:
42
FlaskOAuth2App instance
43
"""
44
45
def create_client(self, name: str = None) -> 'FlaskOAuth2App':
46
"""
47
Create OAuth client by name.
48
49
Args:
50
name: Registered provider name
51
52
Returns:
53
FlaskOAuth2App instance
54
"""
55
56
class FlaskOAuth2App:
57
"""Flask OAuth 2.0 application integration."""
58
59
def __init__(self, name: str, client_id: str, client_secret: str = None, client_kwargs: dict = None, **kwargs) -> None:
60
"""
61
Initialize Flask OAuth 2.0 app.
62
63
Args:
64
name: Application name
65
client_id: OAuth client ID
66
client_secret: OAuth client secret
67
client_kwargs: Additional client configuration
68
**kwargs: Provider configuration
69
"""
70
71
def authorize_redirect(self, redirect_uri: str = None, **kwargs) -> Response:
72
"""
73
Create authorization redirect response.
74
75
Args:
76
redirect_uri: Callback URI
77
**kwargs: Additional authorization parameters
78
79
Returns:
80
Flask redirect response
81
"""
82
83
def authorize_access_token(self, **kwargs) -> dict:
84
"""
85
Handle authorization callback and fetch access token.
86
87
Args:
88
**kwargs: Additional token parameters
89
90
Returns:
91
Token dictionary
92
"""
93
94
def parse_id_token(self, token: dict, nonce: str = None, claims_options: dict = None) -> dict:
95
"""
96
Parse and validate OpenID Connect ID token.
97
98
Args:
99
token: Token dictionary containing id_token
100
nonce: Expected nonce value
101
claims_options: Claims validation options
102
103
Returns:
104
ID token claims dictionary
105
"""
106
107
def get(self, url: str, token: dict = None, **kwargs) -> Response:
108
"""
109
Make authenticated GET request.
110
111
Args:
112
url: Request URL
113
token: Access token dictionary
114
**kwargs: Additional request parameters
115
116
Returns:
117
HTTP response
118
"""
119
120
def post(self, url: str, token: dict = None, **kwargs) -> Response:
121
"""
122
Make authenticated POST request.
123
124
Args:
125
url: Request URL
126
token: Access token dictionary
127
**kwargs: Additional request parameters
128
129
Returns:
130
HTTP response
131
"""
132
133
def put(self, url: str, token: dict = None, **kwargs) -> Response:
134
"""
135
Make authenticated PUT request.
136
137
Args:
138
url: Request URL
139
token: Access token dictionary
140
**kwargs: Additional request parameters
141
142
Returns:
143
HTTP response
144
"""
145
146
def delete(self, url: str, token: dict = None, **kwargs) -> Response:
147
"""
148
Make authenticated DELETE request.
149
150
Args:
151
url: Request URL
152
token: Access token dictionary
153
**kwargs: Additional request parameters
154
155
Returns:
156
HTTP response
157
"""
158
159
class FlaskOAuth1App:
160
"""Flask OAuth 1.0 application integration."""
161
162
def __init__(self, name: str, client_id: str, client_secret: str = None, client_kwargs: dict = None, **kwargs) -> None:
163
"""
164
Initialize Flask OAuth 1.0 app.
165
166
Args:
167
name: Application name
168
client_id: OAuth client ID
169
client_secret: OAuth client secret
170
client_kwargs: Additional client configuration
171
**kwargs: Provider configuration
172
"""
173
174
def authorize_redirect(self, callback: str = None, **kwargs) -> Response:
175
"""
176
Create authorization redirect response.
177
178
Args:
179
callback: Callback URI
180
**kwargs: Additional authorization parameters
181
182
Returns:
183
Flask redirect response
184
"""
185
186
def authorize_access_token(self) -> dict:
187
"""
188
Handle authorization callback and fetch access token.
189
190
Returns:
191
Token dictionary
192
"""
193
194
def get(self, url: str, token: dict = None, **kwargs) -> Response:
195
"""Make authenticated GET request."""
196
197
def post(self, url: str, token: dict = None, **kwargs) -> Response:
198
"""Make authenticated POST request."""
199
```
200
201
### Flask OAuth 2.0 Server
202
203
Flask-specific OAuth 2.0 authorization server implementation with Flask integration features.
204
205
```python { .api }
206
class AuthorizationServer:
207
"""Flask OAuth 2.0 authorization server."""
208
209
def __init__(self, app: Flask = None, query_client: callable = None, save_token: callable = None) -> None:
210
"""
211
Initialize Flask authorization server.
212
213
Args:
214
app: Flask application instance
215
query_client: Function to query client by client_id
216
save_token: Function to save issued tokens
217
"""
218
219
def init_app(self, app: Flask, query_client: callable = None, save_token: callable = None) -> None:
220
"""
221
Initialize authorization server with Flask app.
222
223
Args:
224
app: Flask application instance
225
query_client: Function to query client by client_id
226
save_token: Function to save issued tokens
227
"""
228
229
def register_grant(self, grant_cls: type, extensions: list = None) -> None:
230
"""
231
Register a grant type.
232
233
Args:
234
grant_cls: Grant class to register
235
extensions: List of grant extensions
236
"""
237
238
def register_endpoint(self, endpoint: object) -> None:
239
"""
240
Register an endpoint.
241
242
Args:
243
endpoint: Endpoint instance
244
"""
245
246
def create_authorization_response(self, request: Request = None, grant_user: callable = None) -> Response:
247
"""
248
Create authorization response.
249
250
Args:
251
request: Flask request object (uses current request if None)
252
grant_user: Function to grant authorization to user
253
254
Returns:
255
Flask response object
256
"""
257
258
def create_token_response(self, request: Request = None) -> Response:
259
"""
260
Create token response.
261
262
Args:
263
request: Flask request object (uses current request if None)
264
265
Returns:
266
Flask response object
267
"""
268
269
def create_revocation_response(self, request: Request = None) -> Response:
270
"""
271
Create revocation response.
272
273
Args:
274
request: Flask request object (uses current request if None)
275
276
Returns:
277
Flask response object
278
"""
279
280
def create_introspection_response(self, request: Request = None) -> Response:
281
"""
282
Create introspection response.
283
284
Args:
285
request: Flask request object (uses current request if None)
286
287
Returns:
288
Flask response object
289
"""
290
291
class ResourceProtector:
292
"""Flask OAuth 2.0 resource protector."""
293
294
def __init__(self) -> None:
295
"""Initialize Flask resource protector."""
296
297
def register_token_validator(self, validator: 'BearerTokenValidator') -> None:
298
"""
299
Register bearer token validator.
300
301
Args:
302
validator: Token validator instance
303
"""
304
305
def __call__(self, scopes: list = None, optional: bool = False) -> callable:
306
"""
307
Decorator for protecting Flask routes.
308
309
Args:
310
scopes: Required scopes
311
optional: Whether protection is optional
312
313
Returns:
314
Route decorator function
315
"""
316
317
def acquire_token(self, scopes: list = None, request: Request = None) -> 'OAuth2Token':
318
"""
319
Acquire token from Flask request.
320
321
Args:
322
scopes: Required scopes
323
request: Flask request object
324
325
Returns:
326
OAuth2Token object
327
"""
328
329
# Current token proxy for accessing token in protected routes
330
current_token: LocalProxy
331
```
332
333
### Flask Integration Base Classes
334
335
Base classes for Flask-specific OAuth integrations.
336
337
```python { .api }
338
class FlaskIntegration:
339
"""Flask framework integration base."""
340
341
def __init__(self, oauth: OAuth, name: str, fetch_token: callable = None, update_token: callable = None) -> None:
342
"""
343
Initialize Flask integration.
344
345
Args:
346
oauth: OAuth registry instance
347
name: Provider name
348
fetch_token: Function to fetch stored token
349
update_token: Function to update stored token
350
"""
351
352
def load_server_metadata(self) -> dict:
353
"""
354
Load OAuth server metadata.
355
356
Returns:
357
Server metadata dictionary
358
"""
359
```
360
361
### Token Management
362
363
Flask-specific token management utilities.
364
365
```python { .api }
366
def token_update(token: dict, refresh_token: str = None, access_token: str = None) -> None:
367
"""
368
Update token in Flask session.
369
370
Args:
371
token: Token dictionary to update
372
refresh_token: New refresh token
373
access_token: New access token
374
"""
375
```
376
377
### Flask Signals
378
379
Flask signals for OAuth events.
380
381
```python { .api }
382
# OAuth 2.0 server signals
383
client_authenticated: NamedSignal # Emitted when client is authenticated
384
token_authenticated: NamedSignal # Emitted when token is authenticated
385
token_revoked: NamedSignal # Emitted when token is revoked
386
```
387
388
## Usage Examples
389
390
### OAuth Client Setup
391
392
```python
393
from flask import Flask, session, url_for, redirect, jsonify
394
from authlib.integrations.flask_client import OAuth
395
396
app = Flask(__name__)
397
app.secret_key = 'your-secret-key'
398
399
# Initialize OAuth registry
400
oauth = OAuth(app)
401
402
# Register Google OAuth provider
403
google = oauth.register(
404
name='google',
405
client_id='your-google-client-id',
406
client_secret='your-google-client-secret',
407
server_metadata_url='https://accounts.google.com/.well-known/openid_configuration',
408
client_kwargs={
409
'scope': 'openid email profile'
410
}
411
)
412
413
# Register GitHub OAuth provider
414
github = oauth.register(
415
name='github',
416
client_id='your-github-client-id',
417
client_secret='your-github-client-secret',
418
access_token_url='https://github.com/login/oauth/access_token',
419
access_token_params=None,
420
authorize_url='https://github.com/login/oauth/authorize',
421
authorize_params=None,
422
api_base_url='https://api.github.com/',
423
client_kwargs={'scope': 'user:email'},
424
)
425
426
@app.route('/login/google')
427
def google_login():
428
redirect_uri = url_for('google_callback', _external=True)
429
return google.authorize_redirect(redirect_uri)
430
431
@app.route('/callback/google')
432
def google_callback():
433
token = google.authorize_access_token()
434
435
# Parse ID token for OpenID Connect
436
user_info = google.parse_id_token(token)
437
session['user'] = user_info
438
439
return redirect(url_for('profile'))
440
441
@app.route('/login/github')
442
def github_login():
443
redirect_uri = url_for('github_callback', _external=True)
444
return github.authorize_redirect(redirect_uri)
445
446
@app.route('/callback/github')
447
def github_callback():
448
token = github.authorize_access_token()
449
450
# Get user info from API
451
response = github.get('user', token=token)
452
user_info = response.json()
453
session['user'] = user_info
454
455
return redirect(url_for('profile'))
456
457
@app.route('/profile')
458
def profile():
459
user = session.get('user')
460
if not user:
461
return redirect(url_for('login'))
462
return jsonify(user)
463
```
464
465
### OAuth 2.0 Authorization Server
466
467
```python
468
from flask import Flask, request, session, render_template
469
from authlib.integrations.flask_oauth2 import AuthorizationServer, ResourceProtector
470
from authlib.oauth2.rfc6749.grants import AuthorizationCodeGrant, RefreshTokenGrant
471
from authlib.oauth2.rfc6749.models import ClientMixin, AuthorizationCodeMixin, TokenMixin
472
473
app = Flask(__name__)
474
app.secret_key = 'your-secret-key'
475
476
# Database models
477
class Client(db.Model, ClientMixin):
478
id = db.Column(db.Integer, primary_key=True)
479
client_id = db.Column(db.String(40), unique=True)
480
client_secret = db.Column(db.String(55))
481
482
def get_client_id(self):
483
return self.client_id
484
485
def check_client_secret(self, client_secret):
486
return self.client_secret == client_secret
487
488
class AuthorizationCode(db.Model, AuthorizationCodeMixin):
489
id = db.Column(db.Integer, primary_key=True)
490
user_id = db.Column(db.Integer, nullable=False)
491
code = db.Column(db.String(120), unique=True, nullable=False)
492
client_id = db.Column(db.String(40), nullable=False)
493
494
def get_user_id(self):
495
return self.user_id
496
497
class Token(db.Model, TokenMixin):
498
id = db.Column(db.Integer, primary_key=True)
499
user_id = db.Column(db.Integer, nullable=False)
500
client_id = db.Column(db.String(40), nullable=False)
501
access_token = db.Column(db.String(255), unique=True)
502
refresh_token = db.Column(db.String(255), unique=True)
503
504
def get_user_id(self):
505
return self.user_id
506
507
# Query functions
508
def query_client(client_id):
509
return Client.query.filter_by(client_id=client_id).first()
510
511
def save_token(token, request, *args, **kwargs):
512
if request.grant_type == 'authorization_code':
513
code = request.credential
514
item = Token(
515
client_id=request.client.client_id,
516
user_id=code.get_user_id(),
517
**token
518
)
519
else:
520
item = Token(
521
client_id=request.client.client_id,
522
**token
523
)
524
db.session.add(item)
525
db.session.commit()
526
527
# Initialize authorization server
528
authorization = AuthorizationServer(app, query_client=query_client, save_token=save_token)
529
530
# Register grants
531
authorization.register_grant(AuthorizationCodeGrant)
532
authorization.register_grant(RefreshTokenGrant)
533
534
@app.route('/authorize', methods=['GET', 'POST'])
535
def authorize():
536
user = get_current_user() # Your user authentication
537
538
if request.method == 'GET':
539
try:
540
grant = authorization.validate_consent_request(end_user=user)
541
return render_template('authorize.html', grant=grant, user=user)
542
except OAuth2Error as error:
543
return {'error': error.error}, 400
544
545
if not user:
546
return redirect('/login')
547
548
if request.form.get('confirm'):
549
grant_user = user
550
else:
551
grant_user = None
552
553
return authorization.create_authorization_response(grant_user=grant_user)
554
555
@app.route('/token', methods=['POST'])
556
def issue_token():
557
return authorization.create_token_response()
558
559
@app.route('/revoke', methods=['POST'])
560
def revoke_token():
561
return authorization.create_revocation_response()
562
```
563
564
### Resource Protection
565
566
```python
567
from authlib.integrations.flask_oauth2 import ResourceProtector, current_token
568
from authlib.oauth2.rfc6750 import BearerTokenValidator
569
570
# Token validator
571
class MyBearerTokenValidator(BearerTokenValidator):
572
def authenticate_token(self, token_string):
573
return Token.query.filter_by(access_token=token_string).first()
574
575
def request_invalid(self, request):
576
return False
577
578
def token_revoked(self, token):
579
return token.revoked
580
581
# Initialize resource protector
582
require_oauth = ResourceProtector()
583
require_oauth.register_token_validator(MyBearerTokenValidator())
584
585
@app.route('/api/user')
586
@require_oauth('profile')
587
def api_user():
588
user = get_user_by_id(current_token.user_id)
589
return jsonify({
590
'id': user.id,
591
'username': user.username,
592
'email': user.email
593
})
594
595
@app.route('/api/posts')
596
@require_oauth('read')
597
def api_posts():
598
posts = Post.query.filter_by(user_id=current_token.user_id).all()
599
return jsonify([{
600
'id': post.id,
601
'title': post.title,
602
'content': post.content
603
} for post in posts])
604
605
@app.route('/api/posts', methods=['POST'])
606
@require_oauth('write')
607
def api_create_post():
608
data = request.get_json()
609
post = Post(
610
user_id=current_token.user_id,
611
title=data['title'],
612
content=data['content']
613
)
614
db.session.add(post)
615
db.session.commit()
616
return jsonify({'id': post.id}), 201
617
618
# Optional protection
619
@app.route('/api/public')
620
@require_oauth(optional=True)
621
def api_public():
622
if current_token:
623
return jsonify({'message': f'Hello {current_token.user_id}'})
624
else:
625
return jsonify({'message': 'Hello anonymous'})
626
```
627
628
### Using Flask Signals
629
630
```python
631
from authlib.integrations.flask_oauth2 import client_authenticated, token_authenticated
632
633
@client_authenticated.connect
634
def on_client_authenticated(sender, client=None):
635
print(f'Client {client.client_id} authenticated')
636
# Log client authentication, update metrics, etc.
637
638
@token_authenticated.connect
639
def on_token_authenticated(sender, token=None):
640
print(f'Token for user {token.user_id} authenticated')
641
# Update user last activity, log API usage, etc.
642
643
@token_revoked.connect
644
def on_token_revoked(sender, token=None):
645
print(f'Token {token.access_token} revoked')
646
# Clean up related resources, notify user, etc.
647
```
648
649
### Session-based Token Storage
650
651
```python
652
from flask import session
653
654
def fetch_token(name):
655
"""Fetch token from Flask session."""
656
return session.get(f'{name}_token')
657
658
def update_token(name, token, refresh_token=None, access_token=None):
659
"""Update token in Flask session."""
660
if refresh_token:
661
token['refresh_token'] = refresh_token
662
if access_token:
663
token['access_token'] = access_token
664
session[f'{name}_token'] = token
665
666
# Register provider with token callbacks
667
oauth.register(
668
name='provider',
669
client_id='client-id',
670
client_secret='client-secret',
671
fetch_token=lambda: fetch_token('provider'),
672
update_token=lambda token, **kwargs: update_token('provider', token, **kwargs)
673
)
674
```