PostIt.Android: drive the PKCE flow through Chrome Custom Tabs
The earlier commit removed the client_secret and wired
MainActivity.OnNewIntent to AndroidOidcCallbackSink, but
IdentityModel.OidcClient.LoginAsync still had no IBrowser to drive
the user-agent half of the flow. Without it, the desktop / browser
projects continue to fail at login with 'No browser is available'.
Android now plugs in Chrome Custom Tabs:
* PostIt.Android/Services/AndroidSystemBrowser.cs implements
IBrowser.InvokeAsync using CustomTabsIntent.LaunchUrl and waits
for MainActivity.AndroidOidcCallbackSink to deliver the deep-link
Intent (android://postit-signin?code=...&state=...).
* PostIt/Services/Platform.cs is a tiny static indirection the
shared library uses to ask the running platform for an
IBrowser and the appropriate default RedirectUri, without
referencing any UI framework from the shared assembly.
* LoginPageViewModel reads Platform.DefaultRedirectUri and
Platform.CreateBrowser().Invoke() before calling LoginAsync.
* PostIt.Android/PlatformBootstrap.cs wires the Android side at
startup, and MainActivity.OnCreate calls EnsureInitialized().
* Xamarin.AndroidX.Browser 1.8.0 added to the central package
versions so CustomTabsIntent resolves.
This commit is contained in:
parent
512a0ef06f
commit
c172d1cf9e
7 changed files with 177 additions and 6 deletions
83
src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs
Normal file
83
src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using AndroidX.Browser.CustomTabs;
|
||||
using IdentityModel.OidcClient.Browser;
|
||||
|
||||
namespace PostIt.Android.Services;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IBrowser"/> implementation that drives Chrome Custom Tabs for
|
||||
/// the OIDC Authorization Code + PKCE flow. The identity provider redirects
|
||||
/// to <c>android://postit-signin?code=...&state=...</c>, which Android
|
||||
/// routes back to the running PostIt instance via the activity-alias
|
||||
/// declared in <c>AndroidManifest.xml</c>; the resulting Intent URI is
|
||||
/// handed back through <see cref="MainActivity.AndroidOidcCallbackSink"/>.
|
||||
/// </summary>
|
||||
public sealed class AndroidSystemBrowser : IBrowser
|
||||
{
|
||||
private readonly Activity _activity;
|
||||
|
||||
public AndroidSystemBrowser(Activity activity)
|
||||
{
|
||||
_activity = activity ?? throw new ArgumentNullException(nameof(activity));
|
||||
}
|
||||
|
||||
public async Task<BrowserResult> InvokeAsync(BrowserOptions options, System.Threading.CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options is null) throw new ArgumentNullException(nameof(options));
|
||||
if (string.IsNullOrWhiteSpace(options.StartUrl))
|
||||
{
|
||||
return new BrowserResult
|
||||
{
|
||||
ResultType = BrowserResultType.UnknownError,
|
||||
Error = "BrowserOptions.StartUrl is empty."
|
||||
};
|
||||
}
|
||||
|
||||
var uri = global::Android.Net.Uri.Parse(options.StartUrl)!;
|
||||
|
||||
var callbackTask = MainActivity.AndroidOidcCallbackSink.AwaitNextCallbackAsync();
|
||||
|
||||
var tabsIntent = new CustomTabsIntent.Builder()
|
||||
.SetShowTitle(true)
|
||||
.Build();
|
||||
tabsIntent.LaunchUrl(_activity, uri);
|
||||
|
||||
string responseUri;
|
||||
try
|
||||
{
|
||||
responseUri = await callbackTask.WaitAsync(cancellationToken).ConfigureAwait(true);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return new BrowserResult
|
||||
{
|
||||
ResultType = BrowserResultType.UserCancel
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new BrowserResult
|
||||
{
|
||||
ResultType = BrowserResultType.UnknownError,
|
||||
Error = $"Failed to await OIDC callback: {ex.Message}"
|
||||
};
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(responseUri))
|
||||
{
|
||||
return new BrowserResult
|
||||
{
|
||||
ResultType = BrowserResultType.UserCancel
|
||||
};
|
||||
}
|
||||
|
||||
return new BrowserResult
|
||||
{
|
||||
ResultType = BrowserResultType.Success,
|
||||
Response = responseUri
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue