You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# ...# web app oauth login with google:Authlib==0.15.3# ...
Init file (web application factory pattern):
importosfromflaskimportFlaskfromauthlib.integrations.flask_clientimportOAuthfromdotenvimportload_dotenvfromappimportAPP_ENV, APP_VERSION#from app.firebase_service import FirebaseService#from app.gcal_service import SCOPES as GCAL_SCOPES #, GoogleCalendarServicefromweb_app.routes.home_routesimporthome_routesfromweb_app.routes.user_routesimportuser_routesfromweb_app.routes.calendar_routesimportcalendar_routesload_dotenv()
GA_TRACKER_ID=os.getenv("GA_TRACKER_ID", default="G-OOPS")
SECRET_KEY=os.getenv("SECRET_KEY", default="super secret") # IMPORTANT: override in productionGOOGLE_OAUTH_CLIENT_ID=os.getenv("GOOGLE_OAUTH_CLIENT_ID")
GOOGLE_OAUTH_CLIENT_SECRET=os.getenv("GOOGLE_OAUTH_CLIENT_SECRET")
defcreate_app():
app=Flask(__name__)
app.config["APP_ENV"] =APP_ENVapp.config["APP_VERSION"] =APP_VERSIONapp.config["APP_TITLE"] ="Our Gym Calendar"#app.config["GA_TRACKER_ID"] = GA_TRACKER_ID # for client-side google analyticsapp.config["SECRET_KEY"] =SECRET_KEY# for flask flash messaging#app.config["FIREBASE_SERVICE"] = FirebaseService()#app.config["GCAL_SERVICE"] = GoogleCalendarService()# GOOGLE LOGINoauth=OAuth(app)
oauth_scopes="openid email profile"+" "+" ".join(GCAL_SCOPES)
print("OAUTH SCOPES:", oauth_scopes)
oauth.register(
name="google",
client_id=GOOGLE_OAUTH_CLIENT_ID,
client_secret=GOOGLE_OAUTH_CLIENT_SECRET,
server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
client_kwargs={"scope": oauth_scopes},
authorize_params={"access_type": "offline"} # give us the refresh token! see: https://stackoverflow.com/questions/62293888/obtaining-and-storing-refresh-token-using-authlib-with-flask
) # now you can also access via: oauth.google (the name specified during registration)app.config["OAUTH"] =oauthapp.register_blueprint(home_routes)
app.register_blueprint(user_routes)
app.register_blueprint(calendar_routes)
returnappif__name__=="__main__":
my_app=create_app()
my_app.run(debug=True)
Authentication wrapper for protected routes (requires google login to view, otherwise will be redirected home):
# web_app/routes/auth.py or somethingimportfunctoolsfromflaskimportsession, flash, redirectdefauthenticated_route(view):
""" Wrap a route with this decorator and assume it will have access to the "current_user" See: https://flask.palletsprojects.com/en/2.0.x/tutorial/views/#require-authentication-in-other-views """@functools.wraps(view)defwrapped_view(**kwargs):
ifsession.get("current_user"):
returnview(**kwargs)
else:
print("UNAUTHENTICATED...")
flash("Unauthenticated. Please login!", "warning")
returnredirect("/user/login")
returnwrapped_view
Login routes:
# web_app/routes/user_routes.py or something# SEE:# ... https://docs.authlib.org/en/stable/client/flask.html# ... https://github.com/authlib/demo-oauth-client/tree/master/flask-google-login# ... https://github.com/Vuka951/tutorial-code/blob/master/flask-google-oauth2/app.py# ... https://flask.palletsprojects.com/en/2.0.x/tutorial/views/fromflaskimportBlueprint, render_template, flash, redirect, current_app, url_for, session#, jsonifyfromweb_app.routes.authimportauthenticated_routeuser_routes=Blueprint("user_routes", __name__)
@user_routes.route("/user/login")@user_routes.route("/user/login/form")deflogin_form():
print("LOGIN FORM...")
returnrender_template("user_login_form.html")
@user_routes.route("/user/login/redirect", methods=["POST"])deflogin_redirect():
print("LOGIN REDIRECT...")
oauth=current_app.config["OAUTH"]
redirect_uri=url_for("user_routes.login_callback", _external=True)
returnoauth.google.authorize_redirect(redirect_uri)
@user_routes.route("/user/login/callback")deflogin_callback():
print("LOGIN CALLBACK...")
oauth=current_app.config["OAUTH"]
# user info (below) needs this to be invoked, even if not directly using the token# avoids... authlib.integrations.base_client.errors.MissingTokenError: missing_tokentoken=oauth.google.authorize_access_token()
print("TOKEN:", token["token_type"], token["scope"], token["expires_in"] )
#> {#> 'access_token': '___.___-___-___-___-___-___',#> 'expires_at': 1621201708,#> 'expires_in': 3599,#> 'id_token': '___.___.___-___-___-___-___',#> 'refresh_token': "____",#> 'scope': 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email openid',#> 'token_type': 'Bearer'#> }session["bearer_token"] =token#print(token)user=oauth.google.userinfo()
user=dict(user)
#print("USER INFO:", type(user)) #> <class 'authlib.oidc.core.claims.UserInfo'>print(user)
#> {#> 'email': '[email protected]',#> 'email_verified': True,#> 'family_name': 'Student',#> 'given_name': 'Sammy S',#> 'locale': 'en',#> 'name': 'Sammy S Student',#> 'picture': 'https://lh3.googleusercontent.com/a-/___=___-___',#> 'sub': 'abc123def456789'#> }# store user info in the session:session["current_user"] =userflash(f"Login success. Welcome, {user['given_name']}!", "success")
returnredirect("/user/profile")
@user_routes.route("/user/profile")@authenticated_routedefprofile():
print("PROFILE...")
current_user=session.get("current_user")
returnrender_template("user_profile.html", user=current_user)
@user_routes.route("/user/logout")deflogout():
print("LOGOUT...")
session.clear() # FYI: this clears the flash as well#flash("Logout success!", "success")returnredirect("/user/login")
Login form (view)
{% extends "bootstrap_5_layout.html" %}
{% set active_page = "user_login" %}
{% block content %}
<h1>User Login</h1><p>To access application functionality, first login with your Google Account...</p><formaction="/user/login/redirect" method="POST"><button>Login w/ Google</button></form>
{% endblock %}
Add instructions to the web app exercise, for students looking for further exploration. Snippets below:
Requirements.txt file (uses Authlib package):
Init file (web application factory pattern):
Authentication wrapper for protected routes (requires google login to view, otherwise will be redirected home):
Login routes:
Login form (view)
User profile page:
Navbar with user icon and detection of current logged-in user (goes in twitter bootstrap layout file):
The text was updated successfully, but these errors were encountered: