Skip to content

Commit

Permalink
Unpackaged web auth (#199)
Browse files Browse the repository at this point in the history
* WebAuthenticator: Add support for unpackaged apps
  • Loading branch information
dotMorten authored Nov 21, 2024
1 parent 3a255aa commit dae3bfe
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 20 deletions.
23 changes: 22 additions & 1 deletion docs/concepts/WebAuthenticator.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,29 @@ Next you can make a make a call to authenticate using your default browser:
WebAuthenticatorResult result = await WinUIEx.WebAuthenticator.AuthenticateAsync(authorizeUrl, callbackUri);
```

Your callback uri must use a custom scheme, and you must define this scheme in your application manifest. For example if your callback uri is "myscheme://loggedin", your manifest dialog should look like this:
### Configuration

Your app must be configured for OAuth with schema activation. The scheme must be the first part of the url, ie if your oauth redirection url starts with for instance `myscheme://signin/`, the scheme would be `myscheme`. Note that http(s) schemes are not supported here.

#### Packaged Apps

If your app is packaged, in your app's `Package.appxmanifest` under `Declarations`, add a Protocol declaration and add the scheme you registered for your application's oauth redirect url under "Name".
For example if your callback uri is "myscheme://loggedin", your manifest dialog should look like this:

![image](https://user-images.githubusercontent.com/1378165/166501267-1da07930-ab4d-431e-87cf-a7b183cc3c87.png)

#### Unpackaged Apps

If your app is unpackaged, instead of relying on the app manifest to handle this for you, make sure you register the application for protocol activation. For example:

``` cs
try
{
Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.RegisterForProtocolActivation("myscheme", "Assets\\Square150x150Logo.scale-100", "My App Name", null);
var result = await WebAuthenticator.AuthenticateAsync(authorizeUri, callbackUri, cancellationToken);
}
finally
{
Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.UnregisterForProtocolActivation("myscheme", null);
}
```
4 changes: 2 additions & 2 deletions src/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
<Authors>Morten Nielsen - https://xaml.dev</Authors>
<Company>Morten Nielsen - https://xaml.dev</Company>
<PackageIcon>logo.png</PackageIcon>
<Version>2.5.0</Version>
<!--<PackageValidationBaselineVersion>2.3.4</PackageValidationBaselineVersion>-->
<Version>2.5.1</Version>
<PackageValidationBaselineVersion>2.5.0</PackageValidationBaselineVersion>
</PropertyGroup>

<ItemGroup Condition="'$(PackageId)'!=''">
Expand Down
38 changes: 34 additions & 4 deletions src/WinUIEx/WebAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,30 @@ namespace WinUIEx
/// </summary>
/// <remarks>
/// <para>
/// Your app must be configured for OAuth. In you app package's <c>Package.appxmanifest</c> under Declarations, add a
/// Your app must be configured for OAuth with schema activation. The scheme must be the first part of the url, ie if your
/// oauth redirection url starts with for instance <c>myappscheme://signin/</c>, the scheme would be <c>myappscheme</c>.
/// Note that http(s) schemes are not supported here.
/// </para>
/// <para>
/// If your app is packaged, in your app's <c>Package.appxmanifest</c> under <c>Declarations</c>, add a
/// Protocol declaration and add the scheme you registered for your application's oauth redirect url under "Name".
/// </para>
/// <para>
/// If your app is unpackaged, make sure you register the application for protocol activation.
/// </para>
/// <example>
/// <code lang="csharp">
/// try
/// {
/// Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.RegisterForProtocolActivation("myappscheme", "Assets\\Square150x150Logo.scale-100", "My App Name", null);
/// var result = await WebAuthenticator.AuthenticateAsync(authorizeUri, callbackUri, cancellationToken);
/// }
/// finally
/// {
/// Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.UnregisterForProtocolActivation("myappscheme", null);
/// }
/// </code>
/// </example>
/// </remarks>
public sealed class WebAuthenticator
{
Expand Down Expand Up @@ -218,11 +239,20 @@ private async Task<WebAuthenticatorResult> Authenticate(Uri authorizeUri, Uri ca
}
if (!Helpers.IsAppPackaged)
{
throw new InvalidOperationException("The WebAuthenticator requires a packaged app with an AppxManifest");
if(callbackUri.Scheme == "http" || callbackUri.Scheme == "https")
throw new InvalidOperationException($"{callbackUri.Scheme}:// schemes are not allowed for callbackUri. Use a custom scheme like 'myapp' instead.");
var value = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(callbackUri.Scheme);
if(value is null || value.GetValue("URL Protocol") is null)
{
throw new InvalidOperationException($"The URI Scheme '{callbackUri.Scheme}' is not registered. Call ActivationRegistrationManager.RegisterForProtocolActivation to register protocol activation.");
}
}
if (!IsUriProtocolDeclared(callbackUri.Scheme))
else
{
throw new InvalidOperationException($"The URI Scheme {callbackUri.Scheme} is not declared in AppxManifest.xml");
if (!IsUriProtocolDeclared(callbackUri.Scheme))
{
throw new InvalidOperationException($"The URI Scheme {callbackUri.Scheme} is not declared in AppxManifest.xml");
}
}
var g = Guid.NewGuid();
var taskId = g.ToString();
Expand Down
2 changes: 1 addition & 1 deletion src/WinUIEx/WinUIEx.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<PackageId>WinUIEx</PackageId>
<Product>WinUI Extensions</Product>
<PackageReleaseNotes>
- Added `SimpleSplashScreen` for display application launch splash screens
- WebAuthenticator: Added support for unpackaged apps.
</PackageReleaseNotes>
</PropertyGroup>

Expand Down
24 changes: 12 additions & 12 deletions src/WinUIExSample/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,12 @@ public App()
fss = SimpleSplashScreen.ShowDefaultSplashScreen();
#endif
this.InitializeComponent();
int length = 0;
var sb = new System.Text.StringBuilder(0);
int result = GetCurrentPackageFullName(ref length, sb);
if(result == 15700L)
{
// Not a packaged app. Configure file-based persistence instead
WinUIEx.WindowManager.PersistenceStorage = new FilePersistence("WinUIExPersistence.json");
}

#if UNPACKAGED
// Use file-based persistence since we can't rely on default storage for window persistence when unpackaged
WinUIEx.WindowManager.PersistenceStorage = new FilePersistence("WinUIExPersistence.json");
#endif
}

internal SimpleSplashScreen fss { get; set; }
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
Expand Down Expand Up @@ -67,9 +63,7 @@ private void Splash_Activated(object sender, WindowActivatedEventArgs args)

public WindowEx MainWindow => m_window;

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, System.Text.StringBuilder packageFullName);

#if UNPACKAGED
private class FilePersistence : IDictionary<string, object>
{
private readonly Dictionary<string, object> _data = new Dictionary<string, object>();
Expand Down Expand Up @@ -143,6 +137,7 @@ public void Clear()

IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); // TODO
}
#endif
}

#if DISABLE_XAML_GENERATED_MAIN
Expand All @@ -156,7 +151,11 @@ static void Main(string[] args)
{
if (WebAuthenticator.CheckOAuthRedirectionActivation(true))
return;
#if UNPACKAGED
var fss = SimpleSplashScreen.ShowSplashScreenImage("Assets\\SplashScreen.scale-100.png");
#else
var fss = SimpleSplashScreen.ShowDefaultSplashScreen();
#endif
global::WinRT.ComWrappersSupport.InitializeComWrappers();
global::Microsoft.UI.Xaml.Application.Start((p) => {
var context = new global::Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext(global::Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread());
Expand All @@ -166,4 +165,5 @@ static void Main(string[] args)
}
}
#endif
}
}
10 changes: 10 additions & 0 deletions src/WinUIExSample/Pages/OAuth.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ private async void DoOAuth(string responseType)
OAuthWindow.Visibility = Visibility.Visible;
try
{
#if UNPACKAGED
// Packaged app uses appxmanifest for protocol activation. Unpackaged apps must manually register
Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.RegisterForProtocolActivation("winuiex", "Assets\\Square150x150Logo.scale-100", "WinUI EX", null);
#endif
var result = await WebAuthenticator.AuthenticateAsync(new Uri(authorizeUri), new Uri(callbackUri), oauthCancellationSource.Token);
MainWindow.BringToFront();
OAuthWindow.Visibility = Visibility.Collapsed;
Expand All @@ -75,6 +79,12 @@ private async void DoOAuth(string responseType)
catch (TaskCanceledException) {
Result.Text = "Sign in cancelled";
}
finally
{
#if UNPACKAGED
Microsoft.Windows.AppLifecycle.ActivationRegistrationManager.UnregisterForProtocolActivation("winuiex", null);
#endif
}
}

private void OAuthCancel_Click(object sender, RoutedEventArgs e)
Expand Down
1 change: 1 addition & 0 deletions src/WinUIExSample/WinUIExSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<!--<WindowsPackageType>None</WindowsPackageType>-->
<WindowsSdkPackageVersion>10.0.22621.38</WindowsSdkPackageVersion>
<DefineConstants>DISABLE_XAML_GENERATED_MAIN;$(DefineConstants)</DefineConstants>
<DefineConstants Condition="'$(WindowsPackageType)'=='None'">UNPACKAGED;$(DefineConstants)</DefineConstants>
<PublishAot Condition="'$(RuntimeIdentifier)'=='win-x64'">true</PublishAot>
</PropertyGroup>

Expand Down

0 comments on commit dae3bfe

Please sign in to comment.