I'm looking to incorporate Google authentication into a Dash app. I found a very helpful post on working with Flask here that I followed to set up the Google API access and get the authentication screen to appear allowing the user to login with their Google account. Handling the tokens I get in return is where I get lost. It's not clear to me how to access them or use them properly in further callbacks to get the user verified and logged in.
Ultimately, I need to check the verified email against a separate database before allowing access. That end of the pipeline is working, I'm just missing this authentication piece from Google. Below is a bare-bones example of what I'm trying to do. It will work if you have your API keys set up with Google.
import dash
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
import dash_html_components as html
import dash_core_components as dcc
from flask import redirect, request
from flask_login import logout_user, current_user, LoginManager
import requests
import json
from oauthlib.oauth2 import WebApplicationClient
from flask_login import UserMixin
from sqlalchemy import (Table,
create_engine,
MetaData,
String,
Integer,
DateTime,
Column)
from sqlalchemy.ext.declarative import declarative_base
import auth_utils
app = dash.Dash(__name__)
server = app.server
# Setup the login manager for the server
login_manager = LoginManager()
login_manager.init_app(server)
login_manager.login_view = '/login'
# Set up User class for login manager
Base = declarative_base()
class User(Base, UserMixin):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String, unique=True)
@login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
# Get secret keys
GOOGLE_CLIENT_ID = 'your-keys-here'
GOOGLE_CLIENT_SECRET = 'and-here'
client = WebApplicationClient(GOOGLE_CLIENT_ID)
GOOGLE_DISCOVERY_URL = "https://accounts.google.com/.well-known/openid-configuration"
app.layout = html.Div([
dcc.Location(id='base-url'),
dcc.Location(id='login-url', refresh=True, pathname='/login'),
html.Div(id='hidden-google-redirect-uri',
style={'display': 'none'}),
html.Button('Login with Google',
id='google-login-button',
n_clicks=0),
html.Div(id='login-success-display')
])
@app.callback(
Output('hidden-google-redirect-uri', 'children'),
Input('google-login-button', 'n_clicks'),
)
def google_login(n_clicks):
if n_clicks > 0:
google_provider_cfg = requests.get(GOOGLE_DISCOVERY_URL).json()
google_auth_endpoint = google_provider_cfg["authorization_endpoint"]
request_uri = client.prepare_request_uri(
google_auth_endpoint,
redirect_uri=request.base_url + "/callback",
scope=["openid", "email", "profile"]
)
return dcc.Location(href=request_uri, id='_hidden-google-redirect-uri')
else:
return html.Div()
@app.callback(
Output('login-success-display', 'children'),
Input('google-login-button', 'n_clicks'),
State('hidden-google-redirect-uri', 'children')
)
def google_response(n_clicks, google_response):
if n_clicks == 0:
return html.H3('Not logged in.')
# Get authorization code Google sent back to you
code = request.args.get("code")
# Find out what URL to hit to get tokens that allow you to ask for
# things on behalf of a user
google_provider_cfg = requests.get(GOOGLE_DISCOVERY_URL).json()
token_endpoint = google_provider_cfg["token_endpoint"]
# Prepare and send a request to get tokens
token_url, headers, body = client.prepare_token_request(
token_endpoint,
authorization_response=request.url,
redirect_url=request.base_url,
code=code
)
token_response = requests.post(
token_url,
headers=headers,
data=body,
auth=(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET),
)
client.parse_request_body_response(json.dumps(token_response.json()))
userinfo_endpoint = google_provider_cfg["userinfo_endpoint"]
uri, headers, body = client.add_token(userinfo_endpoint)
userinfo_response = requests.get(uri, headers=headers, data=body)
if userinfo_response.json().get("email_verified"):
unique_id = userinfo_response.json()["sub"]
user_email = userinfo_response.json()["email"]
user_name = userinfo_response.json()["given_name"]
return html.H3('User verified via Google.')
else:
return html.H3('User email not available or not verified by Google.')
if __name__ == "__main__":
app.run_server(port=8800, debug=True)