Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new Windows Forms/Windows Presentation Foundation demos using GitHub authentication #280

Merged
merged 1 commit into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
<NoWarn>$(NoWarn);CS1591;NU5128</NoWarn>
<WarningsNotAsErrors>NU1901;NU1902;NU1903;NU1904</WarningsNotAsErrors>
<DebugSymbols>true</DebugSymbols>
<EnableXlfLocalization>false</EnableXlfLocalization>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
</PropertyGroup>

<PropertyGroup>
Expand Down
6 changes: 5 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
<!--
Note: the following references are exclusively used in the .NET 8.0 and ASP.NET Core 8.0 samples:
-->
<PackageVersion Include="Dapplo.Microsoft.Extensions.Hosting.AppServices" Version="1.0.14" />
<PackageVersion Include="Dapplo.Microsoft.Extensions.Hosting.WinForms" Version="1.0.14" />
<PackageVersion Include="Dapplo.Microsoft.Extensions.Hosting.Wpf" Version="1.0.14" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Negotiate" Version="8.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.1" />
Expand All @@ -77,13 +80,14 @@
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="5.0.1" />
<PackageVersion Include="OpenIddict.AspNetCore" Version="5.0.1" />
<PackageVersion Include="OpenIddict.Client.SystemIntegration" Version="5.0.1" />
<PackageVersion Include="OpenIddict.Client.SystemNetHttp" Version="5.0.1" />
<PackageVersion Include="OpenIddict.Client.WebIntegration" Version="5.0.1" />
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="5.0.1" />
<PackageVersion Include="OpenIddict.Quartz" Version="5.0.1" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="5.0.1" />
Expand Down
19 changes: 18 additions & 1 deletion OpenIddict.Samples.sln
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mortis.Server", "samples\Mo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mortis.Client", "samples\Mortis\Mortis.Client\Mortis.Client.csproj", "{561DF817-8F4F-477A-AD66-DA971E99A666}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zirku.Client2", "samples\Zirku\Zirku.Client2\Zirku.Client2.csproj", "{25D6FF23-937F-4E73-8237-4E805ACDB6C9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Zirku.Client2", "samples\Zirku\Zirku.Client2\Zirku.Client2.csproj", "{25D6FF23-937F-4E73-8237-4E805ACDB6C9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sorgan", "Sorgan", "{F2076FDE-06F9-441B-938E-97953A3C0906}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sorgan.WinForms.Client", "samples\Sorgan\Sorgan.WinForms.Client\Sorgan.WinForms.Client.csproj", "{6E1B3224-B529-4B45-AD66-969BBBA08F63}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sorgan.Wpf.Client", "samples\Sorgan\Sorgan.Wpf.Client\Sorgan.Wpf.Client.csproj", "{5132ABBD-6FC5-4232-B9E1-7F53EC52C826}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -270,6 +276,14 @@ Global
{25D6FF23-937F-4E73-8237-4E805ACDB6C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25D6FF23-937F-4E73-8237-4E805ACDB6C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25D6FF23-937F-4E73-8237-4E805ACDB6C9}.Release|Any CPU.Build.0 = Release|Any CPU
{6E1B3224-B529-4B45-AD66-969BBBA08F63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6E1B3224-B529-4B45-AD66-969BBBA08F63}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6E1B3224-B529-4B45-AD66-969BBBA08F63}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E1B3224-B529-4B45-AD66-969BBBA08F63}.Release|Any CPU.Build.0 = Release|Any CPU
{5132ABBD-6FC5-4232-B9E1-7F53EC52C826}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5132ABBD-6FC5-4232-B9E1-7F53EC52C826}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5132ABBD-6FC5-4232-B9E1-7F53EC52C826}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5132ABBD-6FC5-4232-B9E1-7F53EC52C826}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -323,6 +337,9 @@ Global
{AEE318EE-4D49-4262-9CBE-6CC36051668E} = {036564F9-3EDF-41A3-B2A0-E6BC959E65BA}
{561DF817-8F4F-477A-AD66-DA971E99A666} = {036564F9-3EDF-41A3-B2A0-E6BC959E65BA}
{25D6FF23-937F-4E73-8237-4E805ACDB6C9} = {E0ADFFCA-A604-42D1-8F6D-DE888E061188}
{F2076FDE-06F9-441B-938E-97953A3C0906} = {8B467944-153B-4C90-BAB1-8F1B34C3075A}
{6E1B3224-B529-4B45-AD66-969BBBA08F63} = {F2076FDE-06F9-441B-938E-97953A3C0906}
{5132ABBD-6FC5-4232-B9E1-7F53EC52C826} = {F2076FDE-06F9-441B-938E-97953A3C0906}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F3ECDD26-F40D-4AB4-BC48-8DF143F98FAE}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This repository contains samples demonstrating **how to use [OpenIddict](https:/
- [Imynusoph](samples/Imynusoph): refresh token grant demo, with a .NET console acting as the client.
- [Matty](samples/Matty): device authorization flow demo, with a .NET console acting as the client.
- [Mimban](samples/Mimban): authorization code flow demo using minimal APIs and GitHub delegation for user authentication, with a .NET console acting as the client.
- [Sorgan](samples/Sorgan): Windows Forms and Windows Presentation Foundation clients using GitHub for user authentication.
- [Velusia](samples/Velusia): authorization code flow demo, with an ASP.NET Core application acting as the client.
- [Weytta](samples/Weytta): authorization code flow with Integrated Windows Authentication support and a .NET console acting as the client.
- [Zirku](samples/Zirku): authorization code flow demo using minimal APIs with 2 hard-coded user identities, a .NET console and a SPA acting as the clients and two API projects using introspection (Api1) and local validation (Api2).
Expand Down
13 changes: 9 additions & 4 deletions samples/Matty/Matty.Client/InteractiveService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,24 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)

if (result.VerificationUriComplete is not null)
{
AnsiConsole.MarkupLineInterpolated(
$"[yellow]Please visit [link]{result.VerificationUriComplete}[/] and confirm the displayed code is '{result.UserCode}' to complete the authentication demand.[/]");
AnsiConsole.MarkupLineInterpolated($"""
[yellow]Please visit [link]{result.VerificationUriComplete}[/] and confirm the
displayed code is '{result.UserCode}' to complete the authentication demand.[/]
""");
}

else
{
AnsiConsole.MarkupLineInterpolated(
$"[yellow]Please visit [link]{result.VerificationUri}[/] and enter '{result.UserCode}' to complete the authentication demand.[/]");
AnsiConsole.MarkupLineInterpolated($"""
[yellow]Please visit [link]{result.VerificationUri}[/] and enter
'{result.UserCode}' to complete the authentication demand.[/]
""");
}

// Wait for the user to complete the demand on the other device.
var principal = (await _service.AuthenticateWithDeviceAsync(new()
{
CancellationToken = stoppingToken,
DeviceCode = result.DeviceCode,
Interval = result.Interval,
Timeout = result.ExpiresIn < TimeSpan.FromMinutes(5) ? result.ExpiresIn : TimeSpan.FromMinutes(5)
Expand Down
62 changes: 62 additions & 0 deletions samples/Sorgan/Sorgan.WinForms.Client/MainForm.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 95 additions & 0 deletions samples/Sorgan/Sorgan.WinForms.Client/MainForm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Threading;
using System.Windows.Forms;
using Dapplo.Microsoft.Extensions.Hosting.WinForms;
using OpenIddict.Client;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Abstractions.OpenIddictExceptions;

namespace Sorgan.WinForms.Client;

public partial class MainForm : Form, IWinFormsShell
{
private readonly OpenIddictClientService _service;

public MainForm(OpenIddictClientService service)
{
_service = service ?? throw new ArgumentNullException(nameof(service));

InitializeComponent();
}

private async void LoginButton_Click(object sender, EventArgs e)
{
// Disable the login button to prevent concurrent authentication operations.
LoginButton.Enabled = false;

try
{
using var source = new CancellationTokenSource(delay: TimeSpan.FromSeconds(90));

try
{
// Ask OpenIddict to initiate the authentication flow (typically, by starting the system browser).
var result = await _service.ChallengeInteractivelyAsync(new()
{
CancellationToken = source.Token
});

// Wait for the user to complete the authorization process.
var principal = (await _service.AuthenticateInteractivelyAsync(new()
{
CancellationToken = source.Token,
Nonce = result.Nonce
})).Principal;

TaskDialog.ShowDialog(new TaskDialogPage
{
Caption = "Authentication successful",
Heading = "Authentication successful",
Icon = TaskDialogIcon.ShieldSuccessGreenBar,
Text = $"Welcome, {principal.FindFirst(Claims.Name)!.Value}."
});
}

catch (OperationCanceledException)
{
TaskDialog.ShowDialog(new TaskDialogPage
{
Caption = "Authentication timed out",
Heading = "Authentication timed out",
Icon = TaskDialogIcon.Warning,
Text = "The authentication process was aborted."
});
}

catch (ProtocolException exception) when (exception.Error is Errors.AccessDenied)
{
TaskDialog.ShowDialog(new TaskDialogPage
{
Caption = "Authorization denied",
Heading = "Authorization denied",
Icon = TaskDialogIcon.Warning,
Text = "The authorization was denied by the end user."
});
}

catch
{
TaskDialog.ShowDialog(new TaskDialogPage
{
Caption = "Authentication failed",
Heading = "Authentication failed",
Icon = TaskDialogIcon.Error,
Text = "An error occurred while trying to authenticate the user."
});
}
}

finally
{
// Re-enable the login button to allow starting a new authentication operation.
LoginButton.Enabled = true;
}
}
}
85 changes: 85 additions & 0 deletions samples/Sorgan/Sorgan.WinForms.Client/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.IO;
using Dapplo.Microsoft.Extensions.Hosting.WinForms;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Sorgan.WinForms.Client;

var host = new HostBuilder()
// Note: applications for which a single instance is preferred can reference
// the Dapplo.Microsoft.Extensions.Hosting.AppServices package and call this
// method to automatically close extra instances based on the specified identifier:
//
// .ConfigureSingleInstance(options => options.MutexId = "{7113F751-8CD1-42D8-B294-E5F360497577}")
//
.ConfigureLogging(options => options.AddDebug())
.ConfigureServices(services =>
{
services.AddDbContext<DbContext>(options =>
{
options.UseSqlite($"Filename={Path.Combine(Path.GetTempPath(), "openiddict-sorgan-winforms-client.sqlite3")}");
options.UseOpenIddict();
});

services.AddOpenIddict()

// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
options.UseEntityFrameworkCore()
.UseDbContext<DbContext>();
})

// Register the OpenIddict client components.
.AddClient(options =>
{
// Note: this sample uses the authorization code and refresh token
// flows, but you can enable the other flows if necessary.
options.AllowAuthorizationCodeFlow()
.AllowRefreshTokenFlow();

// Register the signing and encryption credentials used to protect
// sensitive data like the state tokens produced by OpenIddict.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();

// Add the operating system integration.
options.UseSystemIntegration();

// Register the System.Net.Http integration and use the identity of the current
// assembly as a more specific user agent, which can be useful when dealing with
// providers that use the user agent as a way to throttle requests (e.g Reddit).
options.UseSystemNetHttp()
.SetProductInformation(typeof(Program).Assembly);

// Register the Web providers integrations.
//
// Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint
// address per provider, unless all the registered providers support returning an "iss"
// parameter containing their URL as part of authorization responses. For more information,
// see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4.
options.UseWebProviders()
.AddGitHub(options =>
{
options.SetClientId("fa9321227d63cda3f341")
// Note: GitHub doesn't allow creating public clients and requires using a client secret.
.SetClientSecret("d904b9b9ededc39da499b2ea4c13df5c7e35ddbe")
// Note: GitHub doesn't support the recommended ":/" syntax and requires using "://".
.SetRedirectUri("com.openiddict.sorgan.winforms.client://callback/login/github");
});
});

// Register the worker responsible for creating the database used to store tokens
// and adding the registry entries required to register the custom URI scheme.
//
// Note: in a real world application, this step should be part of a setup script.
services.AddHostedService<Worker>();
})
.ConfigureWinForms<MainForm>()
.UseWinFormsLifetime()
.Build();

await host.RunAsync();
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dapplo.Microsoft.Extensions.Hosting.AppServices" />
<PackageReference Include="Dapplo.Microsoft.Extensions.Hosting.WinForms" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="OpenIddict.Client.SystemIntegration" />
<PackageReference Include="OpenIddict.Client.SystemNetHttp" />
<PackageReference Include="OpenIddict.Client.WebIntegration" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" />
</ItemGroup>

</Project>
Loading