Labsco
auth0 logo

auth0-winforms

37

by auth0 · part of auth0/agent-skills

Use when adding Auth0 login, logout, or token management to a Windows Forms (WinForms) desktop application. Integrates Auth0.OidcClient.WinForms — use even if the user says "add login to my WinForms app" or "add Auth0 to my Windows desktop app".

🧩 One of 7 skills in the auth0/agent-skills package — works on its own, and pairs well with its siblings.

This is the playbook your agent receives when the skill activates — you don't need to read it to use the skill, but it's here to audit before installing.

Auth0 WinForms Integration

Add Auth0 authentication to Windows Forms (WinForms) desktop applications. This skill integrates the Auth0.OidcClient.WinForms NuGet package which uses WebView2 to display the Auth0 login page in a popup form for secure OIDC-based login and logout flows with PKCE.

The SDK performs WebAuth (OIDC authorization code flow with PKCE) by opening the Auth0 Universal Login page inside a WebView2 control hosted in a WinForms Form. Unlike native mobile SDKs that require URL scheme registration in Info.plist or AndroidManifest.xml, the WinForms SDK handles the callback entirely within the WebView2 navigation events — no custom URL Types or protocol handlers are needed.

Agent instruction: Before providing SDK setup instructions, fetch the latest release version by running:

gh api repos/auth0/auth0-oidc-client-net/releases --jq '[.[] | select(.tag_name | startswith("winforms-")) | .tag_name | ltrimstr("winforms-")] | .[0]'

Use the returned version in all dependency lines instead of any hardcoded version below.

When NOT to Use

Use CaseRecommended Skill
.NET MAUI cross-platform appauth0-maui
WPF desktop appauth0-wpf
ASP.NET Core server-side web appauth0-aspnetcore-authentication
ASP.NET Core Web API (JWT validation)auth0-aspnetcore-api
iOS-only Swift appauth0-swift
Android-only Kotlin appauth0-android

Done When

  • Auth0.OidcClient.WinForms package installed
  • Auth0Client configured with Domain and ClientId
  • Login/logout flow working (WebView2 popup opens for authentication)
  • User profile claims accessible after login
  • Callback URL https://{yourDomain}/mobile registered in Auth0 Dashboard
  • Build succeeds with no errors
  • Tested on real device (physical Windows machine, not just remote desktop)

Detailed Documentation

  • Setup Guide — Auth0 tenant configuration, SDK installation, callback URL setup
  • Integration Patterns — Login/logout flows, token refresh, user profile, error handling
  • API Reference & Testing — Full Auth0ClientOptions reference, claims, testing checklist, troubleshooting

Testing Notes

Agent instruction: Remind the user to test on a physical device. Some WebView2 behaviors (popup windows, certificate handling) may differ in remote desktop or virtual machine environments vs. physical Windows machines. Test the full login → WebView2 → callback → token flow on real hardware before shipping.

Testing Checklist:

  • Login flow: Click login → WebView2 popup opens → authenticate → popup closes → user info displayed
  • Logout flow: Click logout → WebView2 popup opens → session cleared → popup closes
  • Token refresh: RefreshTokenAsync with stored refresh token works
  • Cancel: User closes WebView2 form → app handles UserCancel gracefully
  • Physical device: Test on a real Windows machine (not just virtual environment)
  • Multiple logins: Verify login works after logout (no stale state)
  • auth0-wpf — WPF desktop apps
  • auth0-maui — .NET MAUI cross-platform apps
  • auth0-aspnetcore-authentication — ASP.NET Core server-side web apps
  • auth0-aspnetcore-api — ASP.NET Core Web API with JWT validation

Quick Reference

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 Complete Example)

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();
    }
}

References