UWP - 解決 WebAuthenticationBroker 無法清除 Cookies

使用 WebAuthenticationBroker 做 Facebook 登入,只要登入過一次,除非用戶從 WebAuthenticationBroker 開啓的網站裏面找到登出或是切換帳號,不然是沒有機會更換帳號或是登出的。這篇將解釋怎麽解決這個問題。

也許你會問什麽不直接使用 Facebook SDK for .NET 或是 UWP Community Toolkit 完成 Facebook Login 就好了。

因爲我需要一個整合多種 OAuth provider 的元件,如果每一種 provider 就加入一個 Nuget 或是 SDK 這樣會讓 App 長的非常大,加上需求只要 Login 功能這樣成本過大。

所以我就參考了 Windows SDK for Facebook 做了一個 XAML custom control 完成 OAuth 與清除 cookiess。

我將 XAML custom control 稱為: OAuthDialog,往下來看一下它的 XAML 與需要做的事情:

1. XAML:


<Style TargetType="control:OAuthDialog" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="control:OAuthDialog">
<Grid x:Name="RootContainer">
	<Border x:Name="BackgroundBorder" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Opacity="0.0" Background="Black" />
	<Grid x:Name="ContentContainer" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="25,0" Background="White">
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="*" />
		</Grid.RowDefinitions>
		<Grid Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="White">
			<Grid.ColumnDefinitions>
				<ColumnDefinition Width="*" />
				<ColumnDefinition Width="Auto" />
			</Grid.ColumnDefinitions>
			<TextBlock Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="10,0" Text="Connecting to a service" FontSize="12" Foreground="#FF888888" />
			<Button Grid.Column="1" x:Name="HideButton" HorizontalAlignment="Right" VerticalAlignment="Center">
				<TextBlock Text="&#xE106;" FontFamily="Segoe MDL2 Assets" />
			</Button>
		</Grid>
		<WebView Grid.Row="1" x:Name="WebViewControl" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
		<ProgressRing Grid.Row="1" x:Name="ProgressRingControl" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FF888888" IsActive="True" Height="80" Width="80" />
	</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

大致上 UI 參考 WebAuthenticationBroker具有一個 WebView,用來處理 Redirect Uri 觸發時,需要通知使用這個 dialog 的對象,準備處理拿到 access token 或是其他任務。

 

2. Code:


public sealed class OAuthDialog : Control
{
    /// <summary>
    /// 處理 Redirect Uri 回傳的事件 
    /// </summary>
    public event EventHandler AuthorizeRedirectChanged; 
    
    public static readonly DependencyProperty AuthorizeUrlProperty = DependencyProperty.Register("AuthorizeUrl", typeof(string), typeof(OAuthDialog), new PropertyMetadata(string.Empty)); 
    public string AuthorizeUrl { 
       get { return (string)GetValue(AuthorizeUrlProperty); }
       set { SetValue(AuthorizeUrlProperty, value); } 
    } 
    
    private WebView WebViewControl { get; set; } 
    protected override void OnApplyTemplate() 
    { 
        base.OnApplyTemplate(); 

        WebViewControl = GetTemplateChild("WebViewControl") as WebView; 
        if (WebViewControl != null) 
        { 
            WebViewControl.UnsupportedUriSchemeIdentified += WebViewControl_UnsupportedUriSchemeIdentified; 

            if (Uri.TryCreate(AuthorizeUrl, UriKind.Absolute, out var authorizeUri)) 
            { 
                WebViewControl.Navigate(authorizeUri); 
            } 
        } 
    } 

    private void WebViewControl_UnsupportedUriSchemeIdentified(WebView sender, WebViewUnsupportedUriSchemeIdentifiedEventArgs args) 
    { 
        // 註冊處理 redirect uri 裏面給的 custome scheme, 例如: ms-app://{store id} 
        AuthorizeRedirectChanged?.Invoke(sender, args.Uri.OriginalString); 
        args.Handled = true; 
        Hide(); 
    } 

    // ====== 以下省略 ====== 
}

利用 WebView 的 UnsupportedUriSchemeIdentified 捕捉 ms-app:// 這個 custom scheme。

如果整合到 Google 或是 Twitter 可能不一定合用(取決於設定 redirect uri 的設定),可以換處理 NavigationStarting 處理過去中 URL 的變化來抓到需要的内容。

OAuthDialog 我加了一個 AuthorizeUrl 的屬性,爲了讓它單純處理 URL 的交易與 cookies 的管理即可,其他的内容都透過自定義的 AuthorizeRedirectChanged 事件抛轉出去。

 

3. 在 OAuthDialog 加入清除 Cookies:


private void ClearCookie()
{
    if (Uri.TryCreate(AuthorizeUrl, UriKind.Absolute, out var authUri))
    {
        // 抓出 OAuth URL 的 domain,並且去掉它所有的 cookies
	string targetUrl = $"{authUri.Scheme}://{authUri.Host}";

	HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
	HttpCookieManager manager = filter.CookieManager;
	HttpCookieCollection collection = manager.GetCookies(new Uri(targetUrl));

	foreach (var item in collection)
	{
		manager.DeleteCookie(item);
	}
    }
}

這一段參考 void FacebookDialog::DeleteCookies(),利用 URL 裏面的 domain 抓出存在的 cookies 再逐一刪除,解決 WebAuthenticationBroker 登入過無法切換帳號的問題。

 

4. 如何使用:


private void OnAuthoirzeDialogClick(object sender, RoutedEventArgs e)
{
    AccessTokenTextBlock.Text = string.Empty;

    string scope = "public_profile,email";
    string fbAppId = "{facebook app id}";
    string appId = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().OriginalString;
    string request = $"https://www.facebook.com/v2.6/dialog/oauth?client_id={fbAppId}&redirect_uri={appId}&response_type=token&scope={scope}&display=popup";

    OAuthDialog oauthdialog = new OAuthDialog();
    oauthdialog.AuthorizeRedirectChanged += (o, a) =>
    {
        int idx = a.IndexOf("access_token");

        if (idx > 0)
        {
            string[] data = a.Split(new string[] { "#" }, StringSplitOptions.RemoveEmptyEntries);
            Dictionary returnData = data[1].Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
                                                        .Select(part => part.Split('='))
                                                        .ToDictionary(split => split[0], split => split[1]);
            AccessTokenTextBlock.Text = returnData["access_token"];
        }
        else
        {
            AccessTokenTextBlock.Text = a;
        }
    };

    oauthdialog.AuthorizeUrl = request;

    oauthdialog.Show();
}

根據 AuthorizeRedirectChanged 拿到的 URL 做分析,拿出 AccessToken。相當方便。

 

[範例程式]

Link: 13-AppWithOAuth

======

以上是把 WebAuthenticationBroker 變成一個自定義的 Dialog control,讓開發人員方便控制 cookies,不然 WebAuthenticationBroker 是方便但是就沒有提供清除 cookies 的方式。

希望對大家有所幫助。

References: