Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Social Oauth example code. #826

Merged
merged 2 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions docs/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
82 changes: 21 additions & 61 deletions examples/oauth/server/app.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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

Expand All @@ -54,7 +57,6 @@ def send_mail(
sender,
body,
html,
user,
**kwargs,
):
flash(f"Email body: {body}")
Expand All @@ -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"
}
Expand All @@ -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
Expand All @@ -107,54 +103,18 @@ 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("/")
@auth_required()
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


Expand Down