关于 auth0-winforms
面向 .NET Windows Forms 应用的 Auth0 OIDC 登录与退出集成技能,指导编码 Agent 完成配置、WebView2 集成、回调设置和构建验证。
主要功能
- 01通过 WebView2 登录 Auth0
- 02OIDC 授权码流程与 PKCE
- 03集成登录和退出
- 04回调 URL 配置指导
- 05构建与测试检查清单
适用场景
- 01为 WinForms 应用添加 Auth0 身份验证
- 02配置登录和退出处理程序
- 03为 Auth0 原生应用设置回调 URL
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ auth0/ agent-skills --skill auth0-winforms安装后在 Claude Code 中输入/auth0-winforms确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api.mdintegration.mdsetup.md详细介绍
Auth0 WinForms 集成
为 Windows Forms(WinForms)桌面应用添加 Auth0 身份验证。此技能集成了 Auth0.OidcClient.WinForms NuGet 包,该包使用 WebView2 在弹出窗口中显示 Auth0 登录页面,以便通过 PKCE 安全地执行基于 OIDC 的登录和登出流程。
SDK 通过在 WinForms Form 中承载的 WebView2 控件内打开 Auth0 Universal Login 页面来执行 WebAuth(使用 PKCE 的 OIDC 授权码流程)。与需要在 Info.plist 或 AndroidManifest.xml 中注册 URL 方案的原生移动 SDK 不同,WinForms SDK 完全通过 WebView2 导航事件处理回调,无需自定义 URL 类型或协议处理程序。
Agent 指令: 在提供 SDK 设置说明之前,运行以下命令,获取最新发布版本:
gh api repos/auth0/auth0-oidc-client-net/releases --jq '[.[] | select(.tag_name | startswith("winforms-")) | .tag_name | ltrimstr("winforms-")] | .[0]'在所有依赖项行中使用返回的版本,而不是下文中任何硬编码的版本。
前提条件
- .NET 6.0 SDK 或更高版本(WinForms 需要 Windows)
- Visual Studio 2022 或 JetBrains Rider,并安装 .NET 桌面开发工作负载
- Windows 10 或更高版本
- Microsoft Edge WebView2 Runtime(Windows 11 已包含,Windows 10 可安装)
- 已配置 Native 应用程序的 Auth0 账户
- Node.js 20+(用于 Auth0 CLI 自动设置)
不适用场景
| 使用场景 | 推荐技能 |
|---|---|
| .NET MAUI 跨平台应用 | auth0-maui |
| WPF 桌面应用 | auth0-wpf |
| ASP.NET Core 服务端 Web 应用 | auth0-aspnetcore-authentication |
| ASP.NET Core Web API(JWT 验证) | auth0-aspnetcore-api |
| 仅适用于 iOS 的 Swift 应用 | auth0-swift |
| 仅适用于 Android 的 Kotlin 应用 | auth0-android |
快速入门工作流
Agent 指令: 开始之前,检查用户的项目:
- 从
.csproj文件的TargetFramework中确定 .NET 版本- 检查代码库中是否已有身份验证提供程序——搜索现有的
Auth0Client或WebViewBrowser用法,如有则复用- 记录项目的命名空间和目录约定
- 安装 SDK:
dotnet add package Auth0.OidcClient.WinForms - 配置 Auth0:参阅设置指南,了解自动或手动配置方式。
- 集成身份验证:添加
Auth0Client初始化,并将登录/登出操作绑定到按钮点击处理程序。 - 构建并验证:
dotnet build
Agent 指令: 编写 Auth0Client 配置时:
- 使用本技能集成指南中的确切代码模式。
- SDK 使用 WebView2 在弹出窗口中显示登录页面——无需自定义浏览器设置。
- 默认回调 URL 为
https://{yourDomain}/mobile——必须将其添加到 Auth0 Dashboard 的 Allowed Callback URLs 和 Allowed Logout URLs 中。- 与使用
https://{domain}/ios/{bundleId}/callback或类似平台专用模式的原生移动 SDK 不同,WinForms 使用更简单的https://{domain}/mobile回调格式。编写配置和代码后,验证构建是否成功:
dotnet build如果构建失败,请尝试修复问题。连续失败 5-6 次后,请向用户寻求帮助。
回调 URL 配置
WinForms SDK 默认使用 https://{yourDomain}/mobile 作为回调 URL。这与移动原生 SDK 不同:
- 移动 SDK 使用平台专属的回调,例如
https://{domain}/ios/{bundleId}/callback或https://{domain}/android/{packageName}/callback - WPF/WinForms 使用通用的
https://{yourDomain}/mobile回调
回调由 WebView2 控件的 NavigationStarting 事件拦截 — 无需注册系统级 URL 方案。你不需要配置 Info.plist、AndroidManifest.xml 或 Windows 协议处理程序。
在 Auth0 Dashboard 中配置:
- Allowed Callback URLs:
https://{yourDomain}/mobile - Allowed Logout URLs:
https://{yourDomain}/mobile
完成条件
- 已安装
Auth0.OidcClient.WinForms包 - 已使用 Domain 和 ClientId 配置
Auth0Client - 登录/注销流程正常工作(WebView2 弹出窗口打开并进行身份验证)
- 登录后可以访问用户个人资料声明
- 已在 Auth0 Dashboard 中注册回调 URL
https://{yourDomain}/mobile - 构建成功且没有错误
- 已在真实设备上测试(实体 Windows 计算机,而不仅是远程桌面)
详细文档
- 设置指南 — Auth0 租户配置、SDK 安装、回调 URL 设置
- 集成模式 — 登录/注销流程、令牌刷新、用户个人资料、错误处理
- API 参考与测试 — 完整的
Auth0ClientOptions参考、声明、测试清单、故障排除
常见错误
| 错误 | 修复方法 |
|---|---|
| Auth0 Dashboard 中的应用类型未设置为 Native | 在 Dashboard 设置中将应用类型改为“Native” |
| Auth0 Dashboard 中缺少回调 URL | 将 https://{yourDomain}/mobile 添加到 Allowed Callback URLs 和 Allowed Logout URLs |
在 Domain 配置中使用 https:// 前缀 | Domain 应仅包含主机名(例如 tenant.auth0.com,而不是 https://tenant.auth0.com) |
| 未安装 WebView2 Runtime | 在 Windows 10 上安装 Microsoft Edge WebView2 Runtime(Windows 11 已包含) |
刷新令牌时未请求 offline_access 范围 | 将 offline_access 添加到 Auth0ClientOptions 中的 Scope |
| 在代码中存储 ClientSecret | 原生应用不会使用 Client Secret — 请将其移除 |
| 尝试在注册表/清单中注册 URL 方案 | WinForms 在进程内使用 WebView2 — 无需注册 URL 方案(与需要 AppxManifest 的 MAUI 不同) |
测试说明
Agent 指令: 提醒用户在实体设备上测试。某些 WebView2 行为(弹出窗口、证书处理)在远程桌面或虚拟机环境中可能与实体 Windows 计算机有所不同。发布前,请在真实硬件上测试完整的登录 → WebView2 → 回调 → 令牌流程。
测试清单:
- 登录流程:点击登录 → WebView2 弹出窗口打开 → 完成身份验证 → 弹出窗口关闭 → 显示用户信息
- 注销流程:点击注销 → WebView2 弹出窗口打开 → 清除会话 → 弹出窗口关闭
- 令牌刷新:使用已存储的刷新令牌调用
RefreshTokenAsync可正常工作 - 取消:用户关闭 WebView2 窗体 → 应用妥善处理
UserCancel - 实体设备:在真实 Windows 计算机上测试(而不仅是虚拟环境)
- 多次登录:确认注销后仍可正常登录(没有残留状态)
相关技能
- auth0-wpf — WPF 桌面应用
- auth0-maui — .NET MAUI 跨平台应用
- auth0-aspnetcore-authentication — ASP.NET Core 服务端 Web 应用
- auth0-aspnetcore-api — 使用 JWT 验证的 ASP.NET Core Web API
快速参考
using Auth0.OidcClient;
using System.Diagnostics;
// Initialize client
var client = new Auth0Client(new Auth0ClientOptions
{
Domain = "{yourDomain}",
ClientId = "{yourClientId}",
Scope = "openid profile email offline_access"
});
// Login — opens WebView2 popup form (WebAuth flow with PKCE)
var loginResult = await client.LoginAsync();
if (loginResult.IsError == false)
{
var user = loginResult.User;
var name = user.FindFirst(c => c.Type == "name")?.Value;
var email = user.FindFirst(c => c.Type == "email")?.Value;
var picture = user.FindFirst(c => c.Type == "picture")?.Value;
Debug.WriteLine($"name: {name}");
Debug.WriteLine($"email: {email}");
foreach (var claim in loginResult.User.Claims)
{
Debug.WriteLine($"{claim.Type} = {claim.Value}");
}
}
// Logout
await client.LogoutAsync();
// Refresh token (requires offline_access scope)
var refreshToken = loginResult.RefreshToken;
var refreshResult = await client.RefreshTokenAsync(refreshToken);
if (refreshResult.IsError == false)
{
var newAccessToken = refreshResult.AccessToken;
}
Form1.cs(WinForms 完整示例)
using Auth0.OidcClient;
using System.Diagnostics;
namespace MyApp;
public partial class Form1 : Form
{
private Auth0Client _client;
private Button loginButton;
private Button logoutButton;
public Form1()
{
InitializeComponent();
_client = new Auth0Client(new Auth0ClientOptions
{
Domain = "{yourDomain}",
ClientId = "{yourClientId}",
Scope = "openid profile email offline_access"
});
loginButton = new Button
{
Text = "Log In",
Width = 120,
Height = 40,
Left = (ClientSize.Width - 120) / 2,
Top = (ClientSize.Height - 40) / 2
};
loginButton.Click += loginButton_Click;
Controls.Add(loginButton);
logoutButton = new Button
{
Text = "Log Out",
Width = 120,
Height = 40,
Left = (ClientSize.Width - 120) / 2,
Top = (ClientSize.Height - 40) / 2 + 50
};
logoutButton.Click += logoutButton_Click;
Controls.Add(logoutButton);
}
private async void loginButton_Click(object sender, EventArgs e)
{
var loginResult = await _client.LoginAsync();
if (loginResult.IsError)
{
Debug.WriteLine($"Error: {loginResult.Error}");
Debug.WriteLine($"Description: {loginResult.ErrorDescription}");
return;
}
var user = loginResult.User;
var name = user.FindFirst(c => c.Type == "name")?.Value;
var email = user.FindFirst(c => c.Type == "email")?.Value;
var picture = user.FindFirst(c => c.Type == "picture")?.Value;
Debug.WriteLine($"name: {name}");
Debug.WriteLine($"email: {email}");
foreach (var claim in loginResult.User.Claims)
{
Debug.WriteLine($"{claim.Type} = {claim.Value}");
}
}
private async void logoutButton_Click(object sender, EventArgs e)
{
await _client.LogoutAsync();
}
}