├── .github ├── renovate.json └── workflows │ ├── pre-commit.yml │ └── release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── pyproject.toml └── social_flask ├── __init__.py ├── models.py ├── routes.py ├── strategy.py ├── template_filters.py └── utils.py /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | ":dependencyDashboard", 6 | "helpers:pinGitHubActionDigests" 7 | ], 8 | "automerge": true, 9 | "automergeType": "pr", 10 | "automergeStrategy": "rebase", 11 | "platformAutomerge": true, 12 | "pre-commit": { 13 | "enabled": true 14 | }, 15 | "customManagers": [ 16 | { 17 | "customType": "regex", 18 | "fileMatch": [ 19 | "\\.pre-commit-config\\.yaml" 20 | ], 21 | "matchStrings": [ 22 | "(?[^'\" ]+)==(?[^'\" ,\\s]+)" 23 | ], 24 | "datasourceTemplate": "pypi", 25 | "versioningTemplate": "pep440" 26 | }, 27 | { 28 | "customType": "regex", 29 | "fileMatch": [ 30 | "\\.pre-commit-config\\.yaml" 31 | ], 32 | "matchStrings": [ 33 | "(?[^'\" ]+)@(?[^'\" ,\\s]+)" 34 | ], 35 | "datasourceTemplate": "npm", 36 | "versioningTemplate": "npm" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit check 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | pre-commit: 12 | uses: python-social-auth/social-core/.github/workflows/pre-commit-shared.yml@master 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | release: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | id-token: write 15 | steps: 16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | with: 18 | persist-credentials: false 19 | - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 20 | 21 | - run: uv build 22 | 23 | - name: Verify wheel install 24 | run: | 25 | uv venv .venv-install-whl 26 | source .venv-install-whl/bin/activate 27 | uv pip install dist/*.whl 28 | 29 | - name: Verify source install 30 | run: | 31 | uv venv .venv-install-tar 32 | source .venv-install-tar/bin/activate 33 | uv pip install dist/*.tar.gz 34 | 35 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 36 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') 37 | with: 38 | name: dist 39 | path: | 40 | dist/*.tar.gz 41 | dist/*.whl 42 | 43 | - run: uvx twine check dist/* 44 | - run: uvx pydistcheck --inspect dist/* 45 | - run: uvx pyroma dist/*.tar.gz 46 | - run: uvx check-wheel-contents dist/*.whl 47 | - run: uvx check-manifest -v 48 | 49 | - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') 50 | run: uv publish --trusted-publishing always 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | # PyCharm 38 | .idea/ 39 | 40 | test.db 41 | local_settings.py 42 | sessions/ 43 | _build/ 44 | fabfile.py 45 | changelog.sh 46 | 47 | .DS_Store 48 | .\#* 49 | \#*\# 50 | 51 | .python-version 52 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-merge-conflict 10 | - id: check-yaml 11 | - id: check-json 12 | - id: check-toml 13 | - id: check-merge-conflict 14 | - id: debug-statements 15 | - id: mixed-line-ending 16 | args: [--fix=lf] 17 | - id: pretty-format-json 18 | args: [--no-sort-keys, --autofix, --no-ensure-ascii] 19 | - repo: https://github.com/astral-sh/ruff-pre-commit 20 | rev: v0.11.6 21 | hooks: 22 | - id: ruff 23 | args: [--fix, --exit-non-zero-on-fix] 24 | - id: ruff-format 25 | - repo: meta 26 | hooks: 27 | - id: check-hooks-apply 28 | - id: check-useless-excludes 29 | - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks 30 | rev: v2.14.0 31 | hooks: 32 | - id: pretty-format-yaml 33 | args: [--autofix, --indent, '2'] 34 | - repo: https://github.com/pappasam/toml-sort 35 | rev: v0.24.2 36 | hooks: 37 | - id: toml-sort-fix 38 | - repo: https://github.com/abravalheri/validate-pyproject 39 | rev: v0.24.1 40 | hooks: 41 | - id: validate-pyproject 42 | - repo: https://github.com/executablebooks/mdformat 43 | rev: 0.7.22 44 | hooks: 45 | - id: mdformat 46 | additional_dependencies: 47 | - mdformat-gfm==0.4.1 48 | - mdformat-ruff==0.1.3 49 | - mdformat-shfmt==0.2.0 50 | - mdformat_tables==1.0.0 51 | - repo: https://github.com/codespell-project/codespell 52 | rev: v2.4.1 53 | hooks: 54 | - id: codespell 55 | additional_dependencies: 56 | - tomli 57 | - repo: https://github.com/rhysd/actionlint 58 | rev: v1.7.7 59 | hooks: 60 | - id: actionlint 61 | - repo: https://github.com/woodruffw/zizmor-pre-commit 62 | rev: v1.6.0 63 | hooks: 64 | - id: zizmor 65 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [Unreleased] 9 | 10 | ## [1.0.0](https://github.com/python-social-auth/social-app-flask/releases/tag/1.0.0) - 2017-01-22 11 | 12 | ### Changed 13 | 14 | - Remove usage of set/get current strategy methods 15 | 16 | ## [0.1.0](https://github.com/python-social-auth/social-app-flask/releases/tag/0.1.0) - 2016-12-28 17 | 18 | ### Added 19 | 20 | - Added flask-login dependency 21 | 22 | ## [0.0.1](https://github.com/python-social-auth/social-app-flask/releases/tag/0.0.1) - 2016-11-27 23 | 24 | ### Changed 25 | 26 | - Split from the monolitic [python-social-auth](https://github.com/omab/python-social-auth) 27 | codebase 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2016, Matías Aguirre 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of this project nor the names of its contributors may be 15 | used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | global-include *.py 2 | include *.txt CHANGELOG.md LICENSE README.md 3 | recursive-exclude social_flask *.pyc 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Social Auth - Flask 2 | 3 | Python Social Auth is an easy to setup social authentication/registration 4 | mechanism with support for several frameworks and auth providers. 5 | 6 | ## Description 7 | 8 | This is the [Flask](http://flask.pocoo.org/) component of the 9 | [python-social-auth ecosystem](https://github.com/python-social-auth/social-core), 10 | it implements the needed functionality to integrate 11 | [social-auth-core](https://github.com/python-social-auth/social-core) 12 | in a Flask based project using. 13 | 14 | ## Documentation 15 | 16 | Project documentation is available at https://python-social-auth.readthedocs.io/. 17 | 18 | ## Setup 19 | 20 | ```shell 21 | $ pip install social-auth-app-flask 22 | ``` 23 | 24 | ## Contributing 25 | 26 | Contributions are welcome! 27 | 28 | Only the core and Django modules are currently in development. All others are in maintenance only mode, and maintainers are especially welcome there. 29 | 30 | See the [CONTRIBUTING.md](https://github.com/python-social-auth/.github/blob/main/CONTRIBUTING.md) document for details. 31 | 32 | ## Versioning 33 | 34 | This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). 35 | 36 | ## License 37 | 38 | This project follows the BSD license. See the [LICENSE](LICENSE) for details. 39 | 40 | ## Donations 41 | 42 | This project welcomes donations to make the development sustainable, you can fund Python Social Auth on following platforms: 43 | 44 | - [GitHub Sponsors](https://github.com/sponsors/python-social-auth/) 45 | - [Open Collective](https://opencollective.com/python-social-auth) 46 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = ["setuptools>=78.0.2"] 4 | 5 | [project] 6 | authors = [ 7 | {email = "matiasaguirre@gmail.com", name = "Matias Aguirre"} 8 | ] 9 | classifiers = [ 10 | "Development Status :: 4 - Beta", 11 | "Environment :: Web Environment", 12 | "Intended Audience :: Developers", 13 | "Programming Language :: Python :: 2.7", 14 | "Programming Language :: Python :: 3", 15 | "Programming Language :: Python", 16 | "Topic :: Internet" 17 | ] 18 | dependencies = [ 19 | "flask-login", 20 | "six", 21 | "social-auth-core >= 1.0.0" 22 | ] 23 | description = "Python Social Authentication, Flask integration." 24 | dynamic = ["version"] 25 | keywords = [ 26 | "auth", 27 | "flask", 28 | "social" 29 | ] 30 | license = "BSD-3-Clause" 31 | license-files = ["LICENSE"] 32 | name = "social-auth-app-flask" 33 | readme = "README.md" 34 | 35 | [project.urls] 36 | Homepage = "https://github.com/python-social-auth/social-app-flask" 37 | 38 | [tool.setuptools] 39 | include-package-data = false 40 | packages = [ 41 | "social_flask" 42 | ] 43 | zip-safe = false 44 | 45 | [tool.setuptools.dynamic] 46 | version = {attr = "social_flask.__version__"} 47 | 48 | [tool.tomlsort] 49 | ignore_case = true 50 | sort_inline_arrays = true 51 | sort_inline_tables = true 52 | sort_table_keys = true 53 | spaces_before_inline_comment = 2 54 | -------------------------------------------------------------------------------- /social_flask/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.0" 2 | -------------------------------------------------------------------------------- /social_flask/models.py: -------------------------------------------------------------------------------- 1 | # Install any of the storage implementation: 2 | # - social_flask_sqlalchemy (default) 3 | # - social_flask_peewee 4 | # - social_flask_mongoengine 5 | # and define SOCIAL_AUTH_STORAGE setting 6 | -------------------------------------------------------------------------------- /social_flask/routes.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, g, request 2 | from flask_login import login_required, login_user 3 | from social_core.actions import do_auth, do_complete, do_disconnect 4 | 5 | from social_flask.utils import psa 6 | 7 | social_auth = Blueprint("social", __name__) 8 | 9 | 10 | @social_auth.route("/login//", methods=("GET", "POST")) 11 | @psa("social.complete") 12 | def auth(backend): 13 | return do_auth(g.backend) 14 | 15 | 16 | @social_auth.route("/complete//", methods=("GET", "POST")) 17 | @psa("social.complete") 18 | def complete(backend, *args, **kwargs): 19 | """ 20 | Authentication complete view, override this view if transaction 21 | management doesn't suit your needs. 22 | """ 23 | return do_complete(g.backend, login=do_login, user=g.user, *args, **kwargs) 24 | 25 | 26 | @social_auth.route("/disconnect//", methods=("POST",)) 27 | @social_auth.route( 28 | "/disconnect///", methods=("POST",) 29 | ) 30 | @social_auth.route( 31 | "/disconnect///", methods=("POST",) 32 | ) 33 | @login_required 34 | @psa() 35 | def disconnect(backend, association_id=None): 36 | """Disconnects given backend from current logged in user.""" 37 | return do_disconnect(g.backend, g.user, association_id) 38 | 39 | 40 | def do_login(backend, user, social_user): 41 | name = backend.strategy.setting("REMEMBER_SESSION_NAME", "keep") 42 | remember = ( 43 | backend.strategy.session_get(name) 44 | or request.cookies.get(name) 45 | or request.args.get(name) 46 | or request.form.get(name) 47 | or False 48 | ) 49 | return login_user(user, remember=remember) 50 | -------------------------------------------------------------------------------- /social_flask/strategy.py: -------------------------------------------------------------------------------- 1 | from flask import ( 2 | current_app, 3 | make_response, 4 | redirect, 5 | render_template, 6 | render_template_string, 7 | request, 8 | session, 9 | ) 10 | from social_core.strategy import BaseStrategy, BaseTemplateStrategy 11 | from social_core.utils import build_absolute_uri 12 | 13 | 14 | class FlaskTemplateStrategy(BaseTemplateStrategy): 15 | def render_template(self, tpl, context): 16 | return render_template(tpl, **context) 17 | 18 | def render_string(self, html, context): 19 | return render_template_string(html, **context) 20 | 21 | 22 | class FlaskStrategy(BaseStrategy): 23 | DEFAULT_TEMPLATE_STRATEGY = FlaskTemplateStrategy 24 | 25 | def get_setting(self, name): 26 | return current_app.config[name] 27 | 28 | def request_data(self, merge=True): 29 | if merge: 30 | data = request.form.copy() 31 | data.update(request.args) 32 | elif request.method == "POST": 33 | data = request.form 34 | else: 35 | data = request.args 36 | return data 37 | 38 | def request_host(self): 39 | return request.host 40 | 41 | def redirect(self, url): 42 | return redirect(url) 43 | 44 | def html(self, content): 45 | response = make_response(content) 46 | response.headers["Content-Type"] = "text/html;charset=UTF-8" 47 | return response 48 | 49 | def session_get(self, name, default=None): 50 | return session.get(name, default) 51 | 52 | def session_set(self, name, value): 53 | session[name] = value 54 | 55 | def session_pop(self, name): 56 | return session.pop(name, None) 57 | 58 | def session_setdefault(self, name, value): 59 | return session.setdefault(name, value) 60 | 61 | def build_absolute_uri(self, path=None): 62 | return build_absolute_uri(request.host_url, path) 63 | -------------------------------------------------------------------------------- /social_flask/template_filters.py: -------------------------------------------------------------------------------- 1 | from flask import g, request 2 | from social_core.backends.utils import user_backends_data 3 | 4 | from social_flask.utils import get_helper 5 | 6 | 7 | def backends(): 8 | """ 9 | Load Social Auth current user data to context under the key 'backends'. 10 | Will return the output of social.backends.utils.user_backends_data. 11 | """ 12 | return { 13 | "backends": user_backends_data( 14 | g.user, 15 | get_helper("AUTHENTICATION_BACKENDS"), 16 | get_helper("STORAGE", do_import=True), 17 | ) 18 | } 19 | 20 | 21 | def login_redirect(): 22 | """Load current redirect to context.""" 23 | value = request.form.get("next", "") or request.args.get("next", "") 24 | return { 25 | "REDIRECT_FIELD_NAME": "next", 26 | "REDIRECT_FIELD_VALUE": value, 27 | "REDIRECT_QUERYSTRING": (value and ("next=" + value)) or "", 28 | } 29 | -------------------------------------------------------------------------------- /social_flask/utils.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from flask import current_app, g, url_for 4 | from social_core.backends.utils import get_backend 5 | from social_core.utils import get_strategy, module_member, setting_name 6 | 7 | DEFAULTS = { 8 | "STORAGE": "social_flask_sqlalchemy.models.FlaskStorage", 9 | "STRATEGY": "social_flask.strategy.FlaskStrategy", 10 | } 11 | 12 | 13 | def get_helper(name, do_import=False): 14 | config = current_app.config.get(setting_name(name), DEFAULTS.get(name)) 15 | if do_import: 16 | config = module_member(config) 17 | return config 18 | 19 | 20 | def load_strategy(): 21 | return get_strategy(get_helper("STRATEGY"), get_helper("STORAGE")) 22 | 23 | 24 | def load_backend(strategy, name, redirect_uri, *args, **kwargs): 25 | backends = get_helper("AUTHENTICATION_BACKENDS") 26 | Backend = get_backend(backends, name) 27 | return Backend(strategy=strategy, redirect_uri=redirect_uri) 28 | 29 | 30 | def psa(redirect_uri=None): 31 | def decorator(func): 32 | @wraps(func) 33 | def wrapper(backend, *args, **kwargs): 34 | uri = redirect_uri 35 | if uri and not uri.startswith("/"): 36 | uri = url_for(uri, backend=backend) 37 | g.strategy = load_strategy() 38 | g.backend = load_backend( 39 | g.strategy, backend, redirect_uri=uri, *args, **kwargs 40 | ) 41 | return func(backend, *args, **kwargs) 42 | 43 | return wrapper 44 | 45 | return decorator 46 | --------------------------------------------------------------------------------