0
# Strategy Interface
1
2
The strategy interface provides a framework-agnostic layer that adapts social-auth-core to work with any Python web framework. By implementing the strategy pattern, the library can integrate with Django, Flask, Pyramid, FastAPI, or any other web framework while maintaining consistent authentication behavior.
3
4
## Capabilities
5
6
### Base Strategy Class
7
8
The foundational strategy class that defines the interface for framework integration and provides common functionality.
9
10
```python { .api }
11
class BaseStrategy:
12
"""
13
Base strategy class for framework integration.
14
15
The strategy pattern enables social-auth-core to work with any web framework
16
by abstracting request/response handling, session management, and storage operations.
17
"""
18
19
# Class constants
20
ALLOWED_CHARS: str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
21
DEFAULT_TEMPLATE_STRATEGY: type = None # Template strategy class
22
SESSION_SAVE_KEY: str = "psa_session_id" # Session key for saving state
23
24
def __init__(self, storage=None, tpl=None):
25
"""
26
Initialize strategy with storage and template handler.
27
28
Parameters:
29
- storage: Storage backend instance for data persistence
30
- tpl: Template strategy instance for rendering (optional)
31
"""
32
33
def setting(self, name: str, default=None, backend=None):
34
"""
35
Get configuration setting value.
36
37
Retrieves setting values with proper prefixing and backend-specific
38
overrides, following the SOCIAL_AUTH_<BACKEND>_<SETTING> pattern.
39
40
Parameters:
41
- name: Setting name without prefix
42
- default: Default value if setting not found
43
- backend: Backend instance for backend-specific settings
44
45
Returns:
46
Setting value or default
47
"""
48
49
def get_setting(self, name: str):
50
"""
51
Get raw setting value without defaults.
52
53
Parameters:
54
- name: Full setting name
55
56
Returns:
57
Setting value or None if not found
58
59
Raises:
60
NotImplementedError: Must be implemented by subclasses
61
"""
62
63
def request_data(self, merge=True):
64
"""
65
Get request data (GET and POST parameters).
66
67
Extracts data from the current HTTP request, typically combining
68
query parameters and form data into a single dictionary.
69
70
Parameters:
71
- merge: Whether to merge GET and POST data (default: True)
72
73
Returns:
74
Dictionary of request parameters
75
76
Raises:
77
NotImplementedError: Must be implemented by subclasses
78
"""
79
80
def request_host(self):
81
"""
82
Get current request host.
83
84
Returns:
85
Host string from current request
86
87
Raises:
88
NotImplementedError: Must be implemented by subclasses
89
"""
90
91
def redirect(self, url: str):
92
"""
93
Create redirect response.
94
95
Generates a framework-specific redirect response to the specified URL.
96
97
Parameters:
98
- url: URL to redirect to
99
100
Returns:
101
Framework-specific redirect response
102
103
Raises:
104
NotImplementedError: Must be implemented by subclasses
105
"""
106
107
def html(self, content: str):
108
"""
109
Create HTML response.
110
111
Generates a framework-specific HTML response with the provided content.
112
113
Parameters:
114
- content: HTML content string
115
116
Returns:
117
Framework-specific HTML response
118
119
Raises:
120
NotImplementedError: Must be implemented by subclasses
121
"""
122
123
def render_html(self, tpl=None, html=None, context=None):
124
"""
125
Render HTML using template system.
126
127
Parameters:
128
- tpl: Template name/path (optional)
129
- html: Raw HTML string (optional)
130
- context: Template context dictionary (optional)
131
132
Returns:
133
Rendered HTML string
134
"""
135
136
def build_absolute_uri(self, path=None):
137
"""
138
Build absolute URI from relative path.
139
140
Parameters:
141
- path: Relative path (optional)
142
143
Returns:
144
Absolute URI string
145
146
Raises:
147
NotImplementedError: Must be implemented by subclasses
148
"""
149
150
def absolute_uri(self, path=None):
151
"""
152
Alias for build_absolute_uri.
153
154
Parameters:
155
- path: Relative path (optional)
156
157
Returns:
158
Absolute URI string
159
"""
160
161
def get_pipeline(self, backend):
162
"""
163
Get authentication pipeline configuration.
164
165
Returns the list of pipeline functions to execute for authentication,
166
allowing per-backend customization of the authentication flow.
167
168
Parameters:
169
- backend: Authentication backend instance
170
171
Returns:
172
Tuple of pipeline function names
173
"""
174
175
def authenticate(self, *args, **kwargs):
176
"""
177
Authenticate user with backend.
178
179
Executes the authentication process using the specified backend,
180
handling the complete pipeline execution and user creation/retrieval.
181
182
Parameters:
183
- backend: Authentication backend instance
184
- Additional arguments for authentication pipeline
185
186
Returns:
187
Authenticated user instance or None
188
"""
189
190
def clean_authenticate_args(self, *args, **kwargs):
191
"""
192
Clean and validate authentication arguments.
193
194
Processes authentication arguments to ensure they are in the correct
195
format for the pipeline execution.
196
197
Parameters:
198
- Arguments and keyword arguments for authentication
199
200
Returns:
201
Tuple of (cleaned_args, cleaned_kwargs)
202
"""
203
204
def continue_pipeline(self, partial):
205
"""
206
Continue interrupted authentication pipeline.
207
208
Resumes authentication flow from a partial pipeline state,
209
typically after user input or email validation.
210
211
Parameters:
212
- partial: Partial pipeline data object
213
214
Returns:
215
Authentication result or further partial state
216
"""
217
218
def session_get(self, name, default=None):
219
"""
220
Get value from session.
221
222
Parameters:
223
- name: Session key name
224
- default: Default value if key not found
225
226
Returns:
227
Session value or default
228
229
Raises:
230
NotImplementedError: Must be implemented by subclasses
231
"""
232
233
def session_set(self, name, value):
234
"""
235
Set value in session.
236
237
Parameters:
238
- name: Session key name
239
- value: Value to store
240
241
Raises:
242
NotImplementedError: Must be implemented by subclasses
243
"""
244
245
def session_pop(self, name, default=None):
246
"""
247
Remove and return value from session.
248
249
Parameters:
250
- name: Session key name
251
- default: Default value if key not found
252
253
Returns:
254
Session value or default
255
256
Raises:
257
NotImplementedError: Must be implemented by subclasses
258
"""
259
260
def session_setdefault(self, name, value):
261
"""
262
Set session value if key doesn't exist.
263
264
Parameters:
265
- name: Session key name
266
- value: Default value to set
267
268
Returns:
269
Existing value or newly set value
270
"""
271
272
def clean_partial_pipeline(self, token):
273
"""
274
Clean partial pipeline data.
275
276
Removes stored partial pipeline data after completion or cancellation.
277
278
Parameters:
279
- token: Partial pipeline token to clean
280
"""
281
282
def random_string(self, length=12, allowed_chars=None):
283
"""
284
Generate random string.
285
286
Creates a random string for tokens, nonces, or other security purposes.
287
288
Parameters:
289
- length: String length (default: 12)
290
- allowed_chars: Characters to use (default: ALLOWED_CHARS)
291
292
Returns:
293
Random string of specified length
294
"""
295
296
def create_user(self, *args, **kwargs):
297
"""
298
Create new user account.
299
300
Delegates to storage backend to create a new user with provided details.
301
302
Parameters:
303
- User creation arguments
304
305
Returns:
306
New user instance
307
"""
308
309
def get_user(self, user_id):
310
"""
311
Get user by ID.
312
313
Parameters:
314
- user_id: User primary key
315
316
Returns:
317
User instance or None if not found
318
"""
319
```
320
321
### Template Strategy Base Class
322
323
Base class for template rendering strategies that handle HTML output generation.
324
325
```python { .api }
326
class BaseTemplateStrategy:
327
"""
328
Base template strategy for rendering HTML content.
329
330
Provides abstraction for template rendering that can be adapted
331
to different template engines like Jinja2, Django templates, etc.
332
"""
333
334
def __init__(self, strategy):
335
"""
336
Initialize template strategy.
337
338
Parameters:
339
- strategy: Parent strategy instance
340
"""
341
342
def render(self, tpl=None, html=None, context=None):
343
"""
344
Render template or HTML string.
345
346
Parameters:
347
- tpl: Template name/path (optional)
348
- html: Raw HTML string (optional)
349
- context: Template context dictionary (optional)
350
351
Returns:
352
Rendered HTML string
353
354
Raises:
355
ValueError: If neither tpl nor html provided
356
"""
357
358
def render_template(self, tpl: str, context: dict | None):
359
"""
360
Render template file.
361
362
Parameters:
363
- tpl: Template name/path
364
- context: Template context dictionary
365
366
Returns:
367
Rendered HTML string
368
369
Raises:
370
NotImplementedError: Must be implemented by subclasses
371
"""
372
373
def render_string(self, html: str, context: dict | None):
374
"""
375
Render HTML string with context.
376
377
Parameters:
378
- html: Raw HTML string with template syntax
379
- context: Template context dictionary
380
381
Returns:
382
Rendered HTML string
383
384
Raises:
385
NotImplementedError: Must be implemented by subclasses
386
"""
387
```
388
389
## Framework Implementation Examples
390
391
### Django Strategy Implementation
392
393
```python
394
from django.conf import settings
395
from django.shortcuts import redirect
396
from django.http import HttpResponse
397
from social_core.strategy import BaseStrategy
398
399
class DjangoStrategy(BaseStrategy):
400
"""Django framework strategy implementation."""
401
402
def __init__(self, storage, request=None, tpl=None):
403
self.request = request
404
super().__init__(storage, tpl)
405
406
def get_setting(self, name):
407
return getattr(settings, name, None)
408
409
def request_data(self, merge=True):
410
if not self.request:
411
return {}
412
if merge:
413
data = self.request.GET.copy()
414
data.update(self.request.POST)
415
return data
416
return self.request.GET if self.request.method == 'GET' else self.request.POST
417
418
def request_host(self):
419
return self.request.get_host() if self.request else ''
420
421
def redirect(self, url):
422
return redirect(url)
423
424
def html(self, content):
425
return HttpResponse(content, content_type='text/html')
426
427
def build_absolute_uri(self, path=None):
428
return self.request.build_absolute_uri(path) if self.request else path
429
430
def session_get(self, name, default=None):
431
return self.request.session.get(name, default) if self.request else default
432
433
def session_set(self, name, value):
434
if self.request:
435
self.request.session[name] = value
436
437
def session_pop(self, name, default=None):
438
return self.request.session.pop(name, default) if self.request else default
439
```
440
441
### Flask Strategy Implementation
442
443
```python
444
from flask import request, session, redirect as flask_redirect, g
445
from social_core.strategy import BaseStrategy
446
447
class FlaskStrategy(BaseStrategy):
448
"""Flask framework strategy implementation."""
449
450
def __init__(self, storage, tpl=None):
451
super().__init__(storage, tpl)
452
453
def get_setting(self, name):
454
from flask import current_app
455
return current_app.config.get(name)
456
457
def request_data(self, merge=True):
458
if merge:
459
data = request.args.to_dict()
460
data.update(request.form.to_dict())
461
return data
462
return request.args if request.method == 'GET' else request.form
463
464
def request_host(self):
465
return request.host
466
467
def redirect(self, url):
468
return flask_redirect(url)
469
470
def html(self, content):
471
from flask import Response
472
return Response(content, content_type='text/html')
473
474
def build_absolute_uri(self, path=None):
475
return request.url_root.rstrip('/') + (path or '')
476
477
def session_get(self, name, default=None):
478
return session.get(name, default)
479
480
def session_set(self, name, value):
481
session[name] = value
482
483
def session_pop(self, name, default=None):
484
return session.pop(name, default)
485
```
486
487
### FastAPI Strategy Implementation
488
489
```python
490
from fastapi import Request
491
from fastapi.responses import RedirectResponse, HTMLResponse
492
from social_core.strategy import BaseStrategy
493
494
class FastAPIStrategy(BaseStrategy):
495
"""FastAPI framework strategy implementation."""
496
497
def __init__(self, storage, request: Request = None, tpl=None):
498
self.request = request
499
super().__init__(storage, tpl)
500
501
def get_setting(self, name):
502
# Implement settings retrieval for FastAPI
503
return getattr(settings, name, None)
504
505
def request_data(self, merge=True):
506
if not self.request:
507
return {}
508
# FastAPI request data handling
509
return dict(self.request.query_params)
510
511
def request_host(self):
512
return self.request.client.host if self.request else ''
513
514
def redirect(self, url):
515
return RedirectResponse(url=url)
516
517
def html(self, content):
518
return HTMLResponse(content=content)
519
520
def build_absolute_uri(self, path=None):
521
if not self.request:
522
return path
523
base_url = f"{self.request.url.scheme}://{self.request.url.netloc}"
524
return base_url + (path or '')
525
526
def session_get(self, name, default=None):
527
# Implement session handling for FastAPI
528
return getattr(self.request.session, 'get', lambda k, d: d)(name, default)
529
530
def session_set(self, name, value):
531
# Implement session handling for FastAPI
532
if hasattr(self.request, 'session'):
533
self.request.session[name] = value
534
535
def session_pop(self, name, default=None):
536
# Implement session handling for FastAPI
537
if hasattr(self.request, 'session'):
538
return self.request.session.pop(name, default)
539
return default
540
```
541
542
## Configuration Integration
543
544
The strategy interface integrates with framework-specific configuration systems:
545
546
```python
547
# Django settings.py
548
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'your-client-id'
549
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'your-client-secret'
550
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/'
551
SOCIAL_AUTH_PIPELINE = (
552
'social_core.pipeline.social_auth.social_details',
553
# ... additional pipeline steps
554
)
555
556
# Flask config
557
class Config:
558
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'your-client-id'
559
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'your-client-secret'
560
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/'
561
562
# FastAPI settings
563
from pydantic import BaseSettings
564
565
class Settings(BaseSettings):
566
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: str
567
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET: str
568
SOCIAL_AUTH_LOGIN_REDIRECT_URL: str = '/'
569
```
570
571
The strategy interface enables social-auth-core to maintain consistent behavior across different web frameworks while adapting to each framework's specific patterns for request handling, response generation, session management, and configuration.