Skip to content

Commit

Permalink
Add new Windows Forms/Windows Presentation Foundation demos using Git…
Browse files Browse the repository at this point in the history
…Hub authentication
  • Loading branch information
kevinchalet committed Jan 12, 2024
1 parent c12879c commit 9ebebb6
Show file tree
Hide file tree
Showing 17 changed files with 593 additions and 6 deletions.
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

0 comments on commit 9ebebb6

Please sign in to comment.