将 Google 身份验证与 Python 和 Dash 集成

时间:2021-04-13 23:09:12

标签: python authentication flask plotly-dash google-authentication

我希望将 Google 身份验证整合到 Dash 应用中。我找到了一篇关于使用 Flask here 的非常有用的帖子,我遵循它来设置 Google API 访问并显示身份验证屏幕,允许用户使用他们的 Google 帐户登录。处理我得到的代币是我迷路的地方。我不清楚如何访问它们或在进一步的回调中正确使用它们来验证用户并登录。

最终,我需要在允许访问之前根据单独的数据库检查已验证的电子邮件。管道的那一端正在工作,我只是错过了谷歌的这个身份验证部分。 下面是我正在尝试做的一个简单的例子。如果您有 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)

1 个答案:

答案 0 :(得分:1)

遇到类似的情况,我最终使用了 Dash Google OAuth 的修改版本。

虽然图书馆允许通过 flask.request.cookies.get('AUTH-USER') 访问用户的姓名,但鉴于需要获取用户的电子邮件 ID(由 Google 验证),您可以对图书馆的 {{1} 进行以下更改}} 文件:

google_auth.py

...然后通过 ... COOKIE_AUTH_USER_EMAIL = 'AUTH-USER-EMAIL' ... class GoogleAuth(Auth): ... def login_callback(self): ... google = self.__get_google_auth(token=token) resp = google.get(os.environ.get('GOOGLE_AUTH_USER_INFO_URL')) if resp.status_code == 200: user_data = resp.json() r = flask.redirect(flask.session['REDIRECT_URL']) r.set_cookie(COOKIE_AUTH_USER_NAME, user_data['name'], max_age=COOKIE_EXPIRY) # Store (Google) verified user's email ID in a cookie r.set_cookie(COOKIE_AUTH_USER_EMAIL, user_data['email'], max_age=COOKIE_EXPIRY) r.set_cookie(COOKIE_AUTH_ACCESS_TOKEN, token['access_token'], max_age=COOKIE_EXPIRY) flask.session[user_data['name']] = token['access_token'] return r ... ... @staticmethod def logout(): r = flask.redirect('/') r.delete_cookie(COOKIE_AUTH_USER_NAME) # Do not forget to delete your custom cookie r.delete_cookie(COOKIE_AUTH_USER_EMAIL) r.delete_cookie(COOKIE_AUTH_ACCESS_TOKEN) return r (或您为 flask.request.cookies.get('AUTH-USER-EMAIL') 设置的任何值)访问 (Google) 验证用户的电子邮件 ID。

或者,如果您不希望将用户的详细信息(姓名、电子邮件等)存储在 cookie 中,您可以/可以在上述部分中进行更改,以在允许之前根据您的数据库检查用户的电子邮件 ID用户访问,而不是在您的 COOKIE_AUTH_USER_EMAIL 应用代码中这样做。