Skip to content

你可以使用 GitHub、Google、Azure、Auth0、LinkedIn 等第三方身份提供商,在你的 Flet 应用中实现用户身份验证(即「使用 X 登录」按钮)。

身份提供商必须支持 OAuth 2.0 授权码流,才能获取 API 访问令牌。

内置的 Flet 凭据登录与用户管理功能计划在未来版本中推出。如果你需要在应用中创建并管理用户账户,可以自行实现,也可以使用提供丰厚免费层级的 Auth0 身份提供商。

Flet 身份验证特性:

  • 兼容 Flet 桌面、网页以及移动端应用。
  • 允许在单个应用中同时使用多个身份验证提供商。
  • 内置对以下 OAuth 提供商的支持,并能自动拉取用户详细信息:
    • GitHub
    • Azure
    • Google
    • Auth0
  • 支持获取用户所在组(groups)。
  • 支持令牌(Token)自动刷新。
  • 支持使用已保存的令牌登录(即「记住我」功能)。
  • 支持自定义任何兼容 OAuth 的身份验证提供商。

登录流程概述 ​

  1. 配置好 OAuth 提供商(内置或通用),指定 Client ID、Client secret 以及重定向 URL(Redirect URL)。
  2. 调用 await page.login(provider) 启动 OAuth 网页授权流。
  3. 用户被重定向到 OAuth 提供商的官方网站。
  4. 用户在提供商网站上登录,并授权同意应用以所请求的 scope 访问业务 API。
  5. 提供商网站携带着授权码重定向回 Flet 的 OAuth 回调 URL。
  6. Flet 用授权码换取令牌,并调用 page.on_login 事件处理器。
  7. Flet 应用可以通过 await page.auth.get_token() 获取 API 令牌,并从 page.auth.user 读取用户详细信息。

配置 OAuth 提供商 ​

Flet 内置支持以下 OAuth 提供商:GitHub、Azure、Google、Auth0。此外,你也可以配置一个通用 OAuth 提供商,并自行提供授权(authorization)、令牌(token)以及用户信息(user info)端点。

本指南中,我们将演示如何配置以 GitHub 账号登录 Flet。

要将 Flet 身份验证与 GitHub 集成,需先注册一个新的 GitHub OAuth 应用(Profile settings → Developer settings → OAuth Apps)。

Authorization callback URL 应采用如下格式:

{application-url}/oauth_callback

新建 GitHub OAuth 应用

在 OAuth 应用详情页面点击 "Generate a new client secret" 按钮。将 "Client ID" 和 "Client secret" 复制到安全的地方 —— 你会在 Flet 应用中用到它们。

OAuth 应用密钥详情

使用 OAuth 提供商登录 ​

身份验证调用都是异步的。请在 async def 事件处理器或异步的 main() 中使用它们;下面较短的代码片段都假定处于该异步上下文中。

python
import os

import flet as ft
from flet.auth.providers import GitHubOAuthProvider

GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
assert GITHUB_CLIENT_ID, "set GITHUB_CLIENT_ID environment variable"
GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
assert GITHUB_CLIENT_SECRET, "set GITHUB_CLIENT_SECRET environment variable"

def main(page: ft.Page):
    provider = GitHubOAuthProvider(
        client_id=GITHUB_CLIENT_ID,
        client_secret=GITHUB_CLIENT_SECRET,
        redirect_url="http://localhost:8550/oauth_callback",
    )

    async def login_click(e):
        await page.login(provider)

    async def on_login(e):
        if e.error:
            print("Login error:", e.error)
            return
        token = await page.auth.get_token()
        print("Access token:", token.access_token)
        print("User ID:", page.auth.user.id)

    page.on_login = on_login
    page.add(ft.Button("Login with GitHub", on_click=login_click))

ft.run(main, port=8550, view=ft.AppView.WEB_BROWSER)

注意

请注意,我们从环境变量中读取 OAuth 应用的 Client ID 和 Client secret。切勿将任何密钥硬编码进源码,以免不慎暴露给公众!

在运行应用之前,在命令行中设置好这两个密钥环境变量:

$ export GITHUB_CLIENT_ID="<client_id>"
$ export GITHUB_CLIENT_SECRET="<client_secret>"

运行程序并点击 "Login with GitHub" 按钮,GitHub 的授权页面将在以下位置打开:

  • 桌面端应用 —— 一个新的浏览器窗口或标签页。
  • 网页端应用 —— 一个新的弹窗窗口(请确保弹窗拦截已关闭)。
  • 移动端应用 —— 应用内 Web 浏览器。

GitHub 授权页

重定向 URL ​

我们在注册 GitHub OAuth 应用时使用了 http://localhost:8550/oauth_callback 作为重定向 URL。注意它包含固定的端口 8550。要在固定端口上运行 Flet 应用,请在 ft.run() 调用中传入 port 参数:

python
ft.run(main, port=8550)

授权范围 ​

大多数 OAuth 提供商允许应用请求一个或多个 scope,以限制应用对用户账户的访问权限。

Flet 内置的提供商默认会请求访问用户资料的 scope,但你也可以在登录方法中请求额外的 scope,例如上面示例中的 public_repo:

python
await page.login(
    provider,
    scope=["public_repo"]
)

page.login() 方法带有多个用于控制身份验证流程的参数:

  • fetch_user (bool) —— 是否将用户详细信息拉取到 page.auth.user。默认为 True。
  • fetch_groups (bool) —— 是否将用户组拉取到 page.auth.user.groups。默认为 False。
  • scope —— 要请求的 scope 列表。
  • saved_token —— 用于恢复授权的 OAuth 令牌 JSON 快照。用 await page.auth.get_token() 获取令牌,以 token.to_json() 序列化,然后加密并保存到客户端存储。见下文。
  • on_open_authorization_url —— 用于打开授权 URL 所在浏览器的回调。见下文。
  • complete_page_html —— 「你已成功通过身份验证,现在可以关闭此页面」页面的自定义 HTML 内容。
  • redirect_to_page (bool) —— 仅用于 Flet web 应用,当授权页在同一浏览器标签页中打开时使用。

await page.login() 返回一个授权对象。默认实现提供:

  • get_token() —— 一个异步方法,返回 OAuth 令牌,并在需要时自动刷新。见下文。
  • user —— 用户详细信息,含一个必填的 id 字段,以及 OAuth 提供商特定的其他字段。
  • provider —— 用于此次授权的 OAuth 提供商实例。

最后一次授权的引用保存在 page.auth 属性中。

如果你的应用允许使用多个 OAuth 提供商授权,你可以把授权保存在会话(session)中,例如:

python
page.session["github_auth"] = await page.login(github_provider)
page.session["google_auth"] = await page.login(google_provider)

检查身份验证结果 ​

无论授权成功还是失败,都会调用 page.on_login 事件处理器。

事件处理器参数 e 是 LoginEvent 类的实例,具有以下属性:

  • error (str) —— OAuth 错误。
  • error_description (str) —— OAuth 错误描述。

当 error 为空字符串时,说明授权成功。

你可以用这个事件处理器来切换登录/注销的界面,例如:

python
import os

import flet as ft
from flet.auth.providers import GitHubOAuthProvider

def main(page: ft.Page):
    provider = GitHubOAuthProvider(
        client_id=os.getenv("GITHUB_CLIENT_ID"),
        client_secret=os.getenv("GITHUB_CLIENT_SECRET"),
        redirect_url="http://localhost:8550/oauth_callback",
    )

    async def login_button_click(e):
        await page.login(provider, scope=["public_repo"])

    def on_login(e: ft.LoginEvent):
        if not e.error:
            toggle_login_buttons()

    def logout_button_click(e):
        page.logout()

    def on_logout(e):
        toggle_login_buttons()

    def toggle_login_buttons():
        login_button.visible = page.auth is None
        logout_button.visible = page.auth is not None
        page.update()

    login_button = ft.Button("Login with GitHub", on_click=login_button_click)
    logout_button = ft.Button("Logout", on_click=logout_button_click)
    toggle_login_buttons()

    page.on_login = on_login
    page.on_logout = on_logout

    page.add(login_button, logout_button)

ft.run(main, port=8550, view=ft.AppView.WEB_BROWSER)

访问用户详细信息 ​

如果调用 page.login() 时传入 fetch_user=True(默认),用户资料会被赋值给 page.auth.user。

所有内置的 OAuth 提供商都实现了 user.id 属性 —— 一个唯一的用户标识符,其值取决于具体提供商(数字、Guid 或 Email),可在你的应用中作为用户的 key 使用。

其余的用户资料属性取决于提供商,可用索引器访问。例如,要打印 GitHub 用户的某些属性:

python
print("Name:", page.auth.user["name"])
print("Login:", page.auth.user["login"])
print("Email:", page.auth.user["email"])

使用 OAuth 令牌 ​

授权成功后,用 token = await page.auth.get_token() 获取 OAuth 令牌。该令牌具有以下属性:

  • access_token —— 访问令牌,用作 API 请求头中的授权令牌。
  • scope —— 令牌的 scope。
  • token_type —— 访问令牌类型,如 Bearer。
  • expires_in —— 可选的、距令牌过期的秒数。
  • expires_at —— 可选的、令牌过期的时刻(time.time() + expires_in)。
  • refresh_token —— 可选的刷新令牌,用于获取新的访问令牌。

使用 token.access_token 来调用提供商的 API。例如,下面这个异步处理器列出了用户的 GitHub 仓库,且不会阻塞应用的事件循环:

python
import asyncio
import requests

async def list_repositories(e):
    token = await page.auth.get_token()
    headers = {"Authorization": f"Bearer {token.access_token}"}
    response = await asyncio.to_thread(
        requests.get, "https://api.github.com/user/repos", headers=headers, timeout=30
    )
    response.raise_for_status()
    for repo in response.json():
        print(repo["full_name"])

INFO

在每次 API 操作前都调用 await page.auth.get_token(),以便 Flet 能刷新已过期的令牌。保留较早操作的令牌、之后再复用其 access_token 会绕过该刷新检查。

保存和恢复授权令牌 ​

要实现持久登录(登录页上的「记住我」复选框),你可以把授权令牌保存到客户端存储,并在用户下次打开 Flet 应用时用它登录。

将授权令牌序列化为 JSON:

python
token = await page.auth.get_token()
jt = token.to_json()

警告

在向客户端存储发送数据前,请对敏感数据进行加密。

Flet 内置了使用对称算法(加密和解密使用同一个密钥)加密文本数据的工具方法。它们采用 cryptography 包中的 Fernet 实现(AES 128 加一些额外加固),并用 PBKDF2 从用户密码句中导出加密密钥。

加密 JSON 令牌:

python
import os
from flet.security import encrypt, decrypt

secret_key = os.getenv("MY_APP_SECRET_KEY")
# 返回 base64 编码的字符串
ejt = encrypt(jt, secret_key)

警告

请注意,我们从环境变量中读取对称加密所用的密钥(或称密码句、密码等)。切勿将任何密钥硬编码进源码,以免不慎暴露给公众!

在运行应用之前,在命令行中设置好该密钥:

$ export MY_APP_SECRET_KEY="<secret>"

现在,加密后的值可以存入客户端存储:

python
prefs = ft.SharedPreferences()

await prefs.set("myapp.auth_token", ejt)

下次用户打开应用时,你可以从客户端存储读取加密令牌,若存在则解密并用于 page.login():

python
ejt = await prefs.get("myapp.auth_token")
if ejt:
    jt = decrypt(ejt, secret_key)
    await page.login(provider, saved_token=jt)

查看完整应用示例。

注销登录 ​

调用 page.logout() 会重置 page.auth 引用,并触发 page.on_logout 事件处理器。

你可以在注销方法中移除已保存的令牌,例如:

python
async def logout_button_click(e):
    await prefs.remove(AUTH_TOKEN_KEY)
    page.logout()

查看完整应用示例。

自定义授权流程 ​

默认情况下,OAuth 授权流发生在新的浏览器窗口/标签页(桌面端应用)、浏览器弹窗(网页端)或应用内 Web 视图(移动端)。

授权流完成后,用户会被重定向到 Flet 的 OAuth 回调页面(/oauth_callback),该页面会尝试用 JavaScript 关闭浏览器窗口/标签页,并在 JavaScript 关闭失败时指导用户手动关闭。

本节仅适用于 Flet 桌面端和网页端应用,因为移动端的应用内 Web 视图可由 Flet 直接关闭,无需依赖 JavaScript。

你可以在 page.login() 方法中自定义「授权完成」页面的内容,例如:

python
complete_page_html = """
<!DOCTYPE html>
<html>
  <head>
    <title>Signed in to MyApp</title>
  </head>
<body>
  <script type="text/javascript">
      window.close();
  </script>
  <p>You've been successfully signed in! You can close this tab or window now.</p>
</body>
</html>
"""

await page.login(
    provider,
    complete_page_html=complete_page_html,
)

你也可以将网页应用改为在同一标签页中打开提供商的授权页,这可能更符合用户习惯,并免去弹窗拦截的麻烦:

python
await page.login(
    provider,
    on_open_authorization_url=lambda url: ft.UrlLauncher().launch_url(url, web_only_window_name="_self"),
    redirect_to_page=True
)

要在新标签页中打开流程(注意将 _self 替换为 _blank):

python
await page.login(
    provider,
    on_open_authorization_url=lambda url: ft.UrlLauncher().launch_url(url, web_only_window_name="_blank")
)

配置自定义 OAuth 提供商 ​

你可以使用 flet.auth.oauth_provider.OAuthProvider 类,在应用中配置任何兼容 OAuth 的身份验证提供商。

按照 LinkedIn 授权码流指南 的说明,我们可以获得配置 LinkedIn OAuth 提供商所需的全部参数,从而允许你的 Flet 应用用户用其 LinkedIn 账号登录:

python
import os

import flet
from flet import Button, Page
from flet.auth import OAuthProvider

def main(page: Page):
    provider = OAuthProvider(
        client_id=os.getenv("LINKEDIN_CLIENT_ID"),
        client_secret=os.getenv("LINKEDIN_CLIENT_SECRET"),
        authorization_endpoint="https://www.linkedin.com/oauth/v2/authorization",
        token_endpoint="https://www.linkedin.com/oauth/v2/accessToken",
        user_endpoint="https://api.linkedin.com/v2/me",
        user_scopes=["r_liteprofile", "r_emailaddress"],
        user_id_fn=lambda u: u["id"],
        redirect_url="http://localhost:8550/oauth_callback",
    )

    async def login_click(e):
        await page.login(provider)

    async def on_login(e):
        if e.error:
            raise RuntimeError(e.error)
        print("User ID:", page.auth.user.id)
        token = await page.auth.get_token()
        print("Access token:", token.access_token)

    page.on_login = on_login
    page.add(Button("Login with LinkedIn", on_click=login_click))

flet.run(main, port=8550, view=flet.AppView.WEB_BROWSER)

必填的提供商设置:

  • client_id
  • client_secret
  • authorization_endpoint
  • token_endpoint
  • redirect_url

与其他示例一样,client ID 和 client secret 从环境变量中读取。


关注我,学习大模型、大数据、Python等热门技术。