diff --git a/CHANGES.rst b/CHANGES.rst index e43c44e0..54aba494 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask-Security release. Version 5.3.0 ------------- -Released TBD +Released July xxx, 2023 This is a minor version bump due to some small backwards incompatible changes to WebAuthn, recoverability (/reset), confirmation (/confirm) and the two factor validity feature. @@ -27,13 +27,14 @@ Fixes - (:pr:`819`) Convert to pyproject.toml, build, remove setup. - (:pr:`823`) the tf_validity feature now ONLY sets a cookie - and the token is no longer returned as part of a JSON response. -- (:pr:`xxx`) Fix login/unified signin templates to properly send CSRF token. Add more tests. +- (:pr:`825`) Fix login/unified signin templates to properly send CSRF token. Add more tests. +- (:pr:`xxx`) Improve Social Oauth example code. Backwards Compatibility Concerns +++++++++++++++++++++++++++++++++ - To align with the W3C WebAuthn Level2 and 3 spec - transports are now part of the registration response. - This has been changed BOTH in the server code (using py_webauth data structures) as well as the sample + This has been changed BOTH in the server code (using webauthn data structures) as well as the sample javascript code. If an application has their own javascript front end code - it might need to be changed. - The tf_validity feature :py:data`SECURITY_TWO_FACTOR_ALWAYS_VALIDATE` used to set a cookie if the request was form based, and return the token as part of a JSON response. Now, this feature is ONLY cookie based and the token diff --git a/docs/features.rst b/docs/features.rst index 73e44daa..7bef1388 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -291,6 +291,9 @@ for details. Note in particular, that you must setup and provide provider specif information - and most importantly - XX_CLIENT_ID and XX_CLIENT_SECRET should be specified as environment variables. +A very simple example of configuring social auth with Flask-Security is available +in the `examples` directory. + .. _Click: https://palletsprojects.com/p/click/ .. _Flask-Login: https://flask-login.readthedocs.org/en/latest/ .. _Flask-WTF: https://flask-wtf.readthedocs.io/en/1.0.x/csrf/ diff --git a/examples/oauth/server/app.py b/examples/oauth/server/app.py index 6340e10b..bd6244bd 100644 --- a/examples/oauth/server/app.py +++ b/examples/oauth/server/app.py @@ -1,20 +1,26 @@ """ -Copyright 2020-2022 by J. Christopher Wagner (jwag). All rights reserved. +Copyright 2020-2023 by J. Christopher Wagner (jwag). All rights reserved. :license: MIT, see LICENSE for more details. -A simple example of utilizing authlib to authenticate users +A simple example of utilizing Flask-Security's oauth glue layer. + +In addition, this example uses unified signin to allow for passwordless registration. +So users can log in only via social auth OR an email link. + +This example also sets up and required CSRF protection for all endpoints. In order to be self contained, to support things like email confirmation and unified sign in with email, we hack a Mail handler that flashes the contents of emails. -This example is designed for a form-based and session cookie based client. +This example is designed for a browser based client. This example uses github as the oauth provider. Before this example will work: 1) on github register a new oauth application and grap the CLIENT_ID and CLIENT_SECRET. These must be passed in as env variables: "GITHUB_CLIENT_ID" and "GITHUB_CLIENT_SECRET". See: https://docs.authlib.org/en/latest/client/flask.html# for details. -2) Register yourself with this application. + (look under profile->settings->developer settings) +2) Register yourself (with your github email) with this application. Note: by default this uses an in-memory DB - so everytime you restart you lose all registrations. To use a real disk DB: @@ -23,16 +29,13 @@ """ import os -from flask import Flask, flash, redirect, render_template_string, url_for +from flask import Flask, flash, render_template_string from flask_security import ( Security, auth_required, - login_user, - lookup_identity, MailUtil, ) from flask_wtf import CSRFProtect -from authlib.integrations.flask_client import OAuth from models import db, user_datastore @@ -54,7 +57,6 @@ def send_mail( sender, body, html, - user, **kwargs, ): flash(f"Email body: {body}") @@ -67,7 +69,6 @@ def create_app(): app.config["SECRET_KEY"] = "pf9Wkove4IKEAXvy-cQkeDPhv9Cb3Ag-wyJILbq_dFw" # PASSWORD_SALT secrets.SystemRandom().getrandbits(128) app.config["SECURITY_PASSWORD_SALT"] = "156043940537155509276282232127182067465" - app.config["SECURITY_TOTP_SECRETS"] = { "1": "TjQ9Qa31VOrfEzuPy4VHQWPCTmRzCnFzMKLxXYiZu9B" } @@ -80,19 +81,14 @@ def create_app(): app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:" - # Turn on all features (except passwordless since that removes normal login) - for opt in [ - "changeable", - "recoverable", - "registerable", - "trackable", - "confirmable", - "two_factor", - "unified_signin", - "webauthn", - ]: - app.config["SECURITY_" + opt.upper()] = True - app.config["SECURITY_MULTI_FACTOR_RECOVERY_CODES"] = True + # Turn on Oauth glue (github only), passwordless registration (with email link) + app.config["SECURITY_REGISTERABLE"] = True + app.config["SECURITY_OAUTH_ENABLE"] = True + app.config["SECURITY_UNIFIED_SIGNIN"] = True + app.config["SECURITY_PASSWORD_REQUIRED"] = False + app.config["SECURITY_US_SIGNIN_REPLACES_LOGIN"] = True + app.config["SECURITY_US_ENABLED_METHODS"] = ["email"] + app.config["SECURITY_OAUTH_BUILTIN_PROVIDERS"] = ["github"] if os.environ.get("SETTINGS"): # Load settings from a file pointed to by SETTINGS @@ -107,25 +103,11 @@ def create_app(): ): app.config[ev] = _find_bool(os.environ.get(ev)) - # Initialize standard Flask extensions - # Enable CSRF on all api endpoints. + # Enable CSRF on all api endpoints for forms and JSON CSRFProtect(app) - # setup authlib. CLIENT_ID/SECRET are from env variables. - oauth = OAuth(app) - oauth.register( - name="github", - access_token_url="https://github.com/login/oauth/access_token", - access_token_params=None, - authorize_url="https://github.com/login/oauth/authorize", - authorize_params=None, - api_base_url="https://api.github.com/", - client_kwargs={"scope": "user:email"}, - ) - app.extensions["oauth"] = oauth - - db.init_app(app) # Setup Flask-Security + db.init_app(app) Security(app, user_datastore, mail_util_cls=FlashMailUtil) @app.route("/") @@ -133,28 +115,6 @@ def create_app(): def home(): return render_template_string("Hello {{ current_user.email }}") - @app.route("/oauthsuccess") - @auth_required() - def authhome(): - return render_template_string("Hello {{ current_user.email }} used oauth!") - - @app.route("/oauthlogin") - def login(): - redirect_uri = url_for("auth", _external=True) - return oauth.github.authorize_redirect(redirect_uri) - - @app.route("/auth") - def auth(): - token = oauth.github.authorize_access_token() - resp = oauth.github.get("user", token=token) - profile = resp.json() - # Not idea since lookup_identity will check all possible identity attributes. - user = lookup_identity(profile["email"]) - if user: - login_user(user) - return redirect("/oauthsuccess") - return redirect("/login") - return app