用Auth0在.NET MAUI应用程序中添加验证功能

692 阅读11分钟

由于有了.NET MAUI,用一个代码库构建桌面和移动应用程序终于成为可能。让我们探讨一下如何使用Auth0为它们添加认证。

.NET MAUI应用

最初宣布的两年后,.NET多平台应用程序用户界面(MAUI)现在已普遍可用。该框架允许开发者为Windows、macOS、iOS和Android建立桌面和移动应用程序,利用.NET生态系统并使用一个代码库。这是.NET生态系统的一个巨大里程碑。事实上,.NET MAUI的发布完成了.NET作为构建任何类型应用程序的统一平台的愿景。

从技术上讲,.NET MAUI是Xamarin.Forms的演变,它仍将被支持,但不会有新的主要版本。它建立在Xamarin的经验之上,并提供了一种更一致的方式来创建多平台应用程序。像Xamarin一样,.NET MAUI允许你使用C#和XAML创建应用程序。不过,它还是通过增加对单一项目的支持和提供多种方式添加平台特定的代码来简化开发者的体验。从XAML生成的UI控件是性能很好的本地控件,但你也可以在你的本地应用程序中重复使用现有的Blazor组件

.NET MAUI为.NET开发人员带来了令人兴奋的新机会,但本文将重点介绍在一个简单的MAUI应用中添加认证。如果你想了解更多关于.NET MAUI的信息,请阅读这篇博文。如果想了解Xamarin和.NET MAUI的主要区别,请查看这篇文章

先决条件

在写这篇文章的时候,对.NET MAUI的支持还没有完成:在一些主题上仍然缺乏文档(包括对OpenID Connect的认证支持),Visual Studio支持需要Visual Studio 2022预览版,而且许多问题仍然影响着该框架。

请考虑到某些东西可能无法像预期的那样工作,或者在本文发表后可能发生变化。

要构建和运行本文的示例项目,你需要最新的.NET 6.0 SDK。此外,根据你的开发和目标平台,你可能需要额外的组件。请参考本文档以了解更多信息并设置你的开发环境。

示例应用程序

你将建立的样本应用程序是你可以从标准的MAUI模板开始创建的最简单的。本文将使用.NET CLI来创建和构建该项目,以提供一致的跨平台体验,但请自由使用你喜欢的IDE和工具。

要创建示例应用程序,在终端窗口中运行以下命令。

dotnet new maui -o MauiAuth0App

几秒钟后,你会得到一个带有MAUI项目的MauiAuth0App 文件夹。为了确保一切按预期运行,我们将运行我们新创建的应用程序。在这一点上,如果你使用Visual Studio,你可以在运行按钮旁边选择你的目标平台,如下所示。

MAUI target platform in Visual Studio

如果你使用的是.NET CLI,你需要指定目标平台。下面的命令在相应的目标平台上运行你的MAUI应用程序。

# macOS target platform
dotnet build -t:Run -f net6.0-maccatalyst

# Android target platform
dotnet build -t:Run -f net6.0-android

# iOS target platform
dotnet build -t:Run -f net6.0-ios

正如你所看到的,Windows是缺失的。由于一个已知的问题,你目前不能使用.NET CLI在Windows上构建和运行MAUI应用程序。你需要通过msbuild ,然后手动启动该应用,如下面的例子所示。

msbuild -r -p:targetframework=net6.0-windows10.0.19041
MauiAuth0App.exe

在启动你的应用程序后,你会看到一个像下面这样的屏幕。

Running .NET MAUI app

让我们来看看如何将这个应用与Auth0整合。

向Auth0注册

首先,让我们在Auth0注册该应用。使用你的Auth0账户来访问你的仪表板。如果你还没有,你可以免费注册。一旦进入仪表板,移到应用程序部分,并遵循这些步骤。

  1. 单击 "创建应用程序"。
  2. 为您的应用程序提供一个友好的名称(例如,MAUI App),并选择Native作为应用程序类型。
  3. 最后,点击创建按钮。

这些步骤使Auth0知道你的MAUI应用程序。创建应用程序后,移到设置选项卡,注意你的Auth0域和客户ID。你很快就会用到它们。

然后,在同一表格中,向下滚动到应用程序URI部分,并将值分配给 myapp://callback允许回调的URLs允许注销的URLs字段。

第一个值告诉Auth0在用户认证后要回调哪个URL。第二个值告诉Auth0在用户注销后应该重定向到哪个URL。即使你不是在建立一个网络应用程序,你也会学到你的应用程序如何捕捉这个URI。

点击 "保存更改"按钮来应用它们。

添加认证

回到你的MAUI项目中,通过运行以下命令添加OpenID Connect客户端包。

dotnet add package IdentityModel.OidcClient

现在你需要编写代码,将你的应用程序与Auth0集成。你将在几个步骤中完成它。

  • 创建Auth0客户端,即你需要让用户用Auth0认证的组件。
  • 配置你的MAUI应用程序以使用Auth0客户端。
  • 在MAUI应用程序的用户界面上添加登录按钮。
  • 为每个目标平台应用具体的改变。

让我们来看看每个步骤。

创建Auth0客户端

让我们开始构建Auth0客户端,在项目的根文件夹中创建一个Auth0 文件夹。在这个文件夹中,你将创建一个 WebBrowserAuthenticator.cs文件,其内容如下。

// Auth0/WebBrowserAuthenticator.cs

using IdentityModel.Client;
using IdentityModel.OidcClient.Browser;

namespace MauiAuth0App.Auth0;

public class WebBrowserAuthenticator : IdentityModel.OidcClient.Browser.IBrowser
{
  public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
  {
    try
    {
      WebAuthenticatorResult result = await WebAuthenticator.Default.AuthenticateAsync(
          new Uri(options.StartUrl),
          new Uri(options.EndUrl));

      var url = new RequestUrl(options.EndUrl)
          .Create(new Parameters(result.Properties));

      return new BrowserResult
      {
        Response = url,
        ResultType = BrowserResultType.Success
      };
    }
    catch (TaskCanceledException)
    {
      return new BrowserResult
      {
        ResultType = BrowserResultType.UserCancel,
        ErrorDescription = "Login canceled by the user."
      };
    }
  }
}

WebBrowserAuthenticator 类实现了IBrowser 接口来处理认证步骤。在实践中,这个类负责打开系统浏览器,它将向用户显示Auth0通用登录页面。实际的基于浏览器的认证是使用由 Microsoft.Maui.Authentication命名空间提供的WebAuthenticator实例启动。

现在,让我们创建 Auth0ClientOptions.cs文件,并在同一文件夹中加入以下代码。

// Auth0/Auth0ClientOptions.cs

namespace MauiAuth0App.Auth0;

public class Auth0ClientOptions
{
  public Auth0ClientOptions()
  {
    Scope = "openid";
    RedirectUri = "myapp://callback";
    Browser = new WebBrowserAuthenticator();
  }

  public string Domain { get; set; }

  public string ClientId { get; set; }

  public string RedirectUri { get; set; }

  public string Scope { get; set; }

  public IdentityModel.OidcClient.Browser.IBrowser Browser { get; set; }
}

这里定义的Auth0ClientOptions 类收集了Auth0的配置设置。其中,注意到Browser 属性,它是用我们早期定义的WebBrowserAuthenticator 类的一个实例初始化的。

最后,让我们定义实际的Auth0客户端,把 Auth0Client.cs文件添加到Auth0 文件夹中。这是它的内容。

// Auth0/Auth0Client.cs

using IdentityModel.OidcClient;

namespace MauiAuth0App.Auth0;

public class Auth0Client
{
  private readonly OidcClient oidcClient;
  
  public Auth0Client(Auth0ClientOptions options)
  {
    oidcClient = new OidcClient(new OidcClientOptions{
      Authority = $"https://{options.Domain}",
      ClientId = options.ClientId,
      Scope = options.Scope,
      RedirectUri = options.RedirectUri,
      Browser = options.Browser
    });
  }

  public IdentityModel.OidcClient.Browser.IBrowser Browser {
    get {
      return oidcClient.Options.Browser;
    }
    set {
      oidcClient.Options.Browser = value;
    }
  }

  public async Task<LoginResult> LoginAsync() {
    return await oidcClient.LoginAsync();
  }
}

Auth0Client 类的构造函数接收作为参数传递的选项,并创建一个用它们配置的OIDC客户端实例。该类还公开了Browser 属性并提供了 LoginAsync()方法来启动认证过程。

配置你的MAUI应用程序

一旦你有了Auth0客户端,打开 MauiProgram.cs文件,并应用下面强调的修改。

// MauiProgram.cs

using MauiAuth0App.Auth0;    // 👈 new code

namespace MauiAuth0App;

public static class MauiProgram
{
  public static MauiApp CreateMauiApp()
  {
    var builder = MauiApp.CreateBuilder();
    builder
      .UseMauiApp<App>()
      .ConfigureFonts(fonts =>
      {
        fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
        fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
      });

    // 👇 new code
    builder.Services.AddSingleton<MainPage>();

    builder.Services.AddSingleton(new Auth0Client(new()
    {
      Domain = "<YOUR_AUTH0_DOMAIN>",
      ClientId = "<YOUR_CLIENT_ID>",
      Scope = "openid profile",
      RedirectUri = "myapp://callback"
    }));
    // 👆 new code
    
    return builder.Build();
  }
}

首先,添加一个引用到 MauiAuth0App.Auth0命名空间。这使得Auth0客户端在这个上下文中可用。然后,将MainPage 类作为一个单子服务添加到应用程序生成器中。最后,创建一个Auth0客户端的实例,通过所需的选项,并将其作为一个单子服务添加到应用程序构建器中。记住要替换 <YOUR_AUTH0_DOMAIN><YOUR_CLIENT_ID>占位符替换为你的Auth0域和客户端ID的相应值,这些值来自Auth0仪表板。

添加登录按钮

现在,让我们修改用户界面以允许用户进行认证。打开该 MainPage.xaml文件,对其内容做如下修改。

<!-- MainPage.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiAuth0App.MainPage">
             
    <ScrollView>
        <VerticalStackLayout 
            Spacing="25" 
            Padding="30,0" 
            VerticalOptions="Center">
          
            <!-- 👇 new code -->
            <StackLayout
                x:Name="LoginView">
                <Button 
                    x:Name="LoginBtn"
                    Text="Log In"
                    SemanticProperties.Hint="Click to log in"
                    Clicked="OnLoginClicked"
                    HorizontalOptions="Center" />
            </StackLayout>
            <!-- 👆 new code -->

            <!-- 👇 new code -->
            <StackLayout
                x:Name="HomeView"
                IsVisible="false">
            <!-- 👆 new code -->
              <Image
                  Source="dotnet_bot.png"
                  SemanticProperties.Description="Cute dot net bot waving hi to you!"
                  HeightRequest="200"
                  HorizontalOptions="Center" />
                
              <!-- ...existing markup... -->
              
          <!-- 👇 new code -->
          </StackLayout>
          <!-- 👆 new code -->
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

你在现有的文件中添加一个新的 <StackLayout>元素在现有的 <VerticalStackLayout>.这个 <StackLayout>元素被命名为LoginView ,包含名为LoginBtn 的登录按钮。登录按钮在被点击时调用OnLoginClicked 方法。

这个 <StackLayout>元素只是一个UI控件的容器。你将使用它来根据用户的认证状态显示和隐藏控件组。

事实上,除了登录按钮之外,你还将现有的控件包裹在另一个名为 <StackLayout>元素,名为HomeView 。在这种情况下,该 <StackLayout>元素被标记为不可见。这个标记为用户界面做了准备,当应用程序启动时,用户还没有被认证,只显示登录按钮。

为了使用户界面完全发挥作用,请编辑 MainPage.xaml.cs文件,如下所示。

// MainPage.xaml.cs

using MauiAuth0App.Auth0;    // 👈 new code

namespace MauiAuth0App;

public partial class MainPage : ContentPage
{
    int count = 0;
  // 👇 new code
  private readonly Auth0Client auth0Client;
  // 👆 new code

    public MainPage(Auth0Client client)
  // 👆 changed code
    {
        InitializeComponent();
    auth0Client = client;    // 👈 new code
    }

  //...existing code...
  
  // 👇 new code
  private async void OnLoginClicked(object sender, EventArgs e)
  {
    var loginResult = await auth0Client.LoginAsync();

    if (!loginResult.IsError)
    {
      LoginView.IsVisible = false;
      HomeView.IsVisible = true;
    }
    else
    {
      await DisplayAlert("Error", loginResult.ErrorDescription, "OK");
    }
  }
  // 👆 new code
}

在这里,你添加了一个引用到 MauiAuth0App.Auth0命名空间,并声明auth0Client 私有变量。Auth0客户端实例是通过构造函数注入的,并被分配给。 MainPage()构造函数注入Auth0客户端实例,并将其分配给auth0Client私有变量。

然后,你添加了方法的实现。 OnLoginClicked()方法的实现。在这个方法的主体中,你调用了 LoginAsync()方法来开始认证过程。如果一切顺利,你将把LoginView <StackLayout>元素为不可见,并使HomeView <StackLayout>元素是可见的。否则,将显示一个错误对话框。

好了,这个应用程序几乎可以运行了!

应用特定平台的变化

在运行你的应用程序和测试Auth0集成之前,你需要为每个目标平台应用一些变化。你要应用的变化与WebAuthenticator 功能有关。具体来说,你需要指导你的应用程序如何处理回调URI,Auth0将在用户认证后重定向到该URI。

.NET MAUI框架提供了多种方法来添加平台特定的代码、设置和资产。其中一种方式是基于你项目中的Platforms 文件夹的内容。这个文件夹包含每个目标平台的一个子文件夹。只有你想要的目标平台才需要被配置。在本文中,你将专注于Android、iOS、Mac和Windows平台。

安卓设置

移动到 Platforms/Android文件夹,并添加一个新文件,名为 WebAuthenticationCallbackActivity.cs.在其中添加以下内容。

// Platforms/Android/WebAuthenticationCallbackActivity.cs

using Android.App;
using Android.Content.PM;

namespace MauiAuth0App;

[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]
[IntentFilter(new[] { Android.Content.Intent.ActionView },
              Categories = new[] { 
                Android.Content.Intent.CategoryDefault,
                Android.Content.Intent.CategoryBrowsable 
              },
              DataScheme = CALLBACK_SCHEME)]
public class WebAuthenticationCallbackActivity : Microsoft.Maui.Authentication.WebAuthenticatorCallbackActivity
{
    const string CALLBACK_SCHEME = "myapp";
}

这段代码定义了继承自WebAuthenticatorCallbackActivityWebAuthenticationCallbackActivity 类。这个类被标记为一个意图过滤器,接受myapp 作为回调URI的方案。

然后,打开该 Platforms/Android/AndroidManifest.xml文件,并添加下面突出显示的标记,使该意图可见。

<!-- Platforms/Android/AndroidManifest.xml -->

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:allowBackup="true" 
               android:icon="@mipmap/appicon" 
               android:roundIcon="@mipmap/appicon_round" 
               android:supportsRtl="true"></application>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
  <!-- 👇 new code -->
  <queries>
    <intent>
      <action android:name="android.support.customtabs.action.CustomTabsService" />
    </intent>
  </queries>
  <!-- 👆 new code -->
</manifest>

你的MAUI应用程序的安卓版本已经准备好运行了!

Mac和iOS的设置

macOS和iOS的设置是一样的。它们只需要在其特定的文件夹中应用。

对于macOS,移动到 Platforms/MacCatalyst文件夹,打开 Info.plist文件,并添加下图所示的键。

<!-- Platforms/MacCatalyst/Info.plist -->

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  
  <!-- ...existing keys... -->
  
  <!-- 👇 new code -->
  <key>CFBundleURLTypes</key>
  <array>
    <dict>
      <key>CFBundleURLName</key>
      <string>MauiAuth0App</string>
      <key>CFBundleURLSchemes</key>
      <array>
        <string>myapp</string>
      </array>
      <key>CFBundleTypeRole</key>
        <string>Editor</string>
    </dict>
  </array>
  <!-- 👆 new code -->
</dict>
</plist>

将同样的键添加到 Info.plist文件夹中的 Platforms/iOS文件夹中的相同密钥。

Windows设置

要配置你的MAUI应用程序的Windows目标,你应该移动到 Platforms/Windows文件夹,并修改 Package.appxmanifest文件。不幸的是,在写这篇文章的时候,有一个问题。

⚠️目前,由于一个已知的问题,你不能在Windows上使用WebAuthenticator 。⚠️

你将在后面学习另一种方法,将Auth0与你的Windows目标应用程序集成。

无论如何,你将应用到 Package.appxmanifest文件的改动如下。

<?xml version="1.0" encoding="utf-8"?>
<Package>

  <!-- ...existing markup... -->
  
  <Applications>
    <Application Id="App" 
                 Executable="$targetnametoken$.exe" 
                 EntryPoint="$targetentrypoint$">
      <uap:VisualElements
        DisplayName="$placeholder$"
        Description="$placeholder$"
        Square150x150Logo="$placeholder$.png"
        Square44x44Logo="$placeholder$.png"
        BackgroundColor="transparent">
        <uap:DefaultTile Square71x71Logo="$placeholder$.png"
                         Wide310x150Logo="$placeholder$.png"
                         Square310x310Logo="$placeholder$.png" />
        <uap:SplashScreen Image="$placeholder$.png" />
      </uap:VisualElements>
      <!-- 👇 new code -->
      <Extensions>
          <uap:Extension Category="windows.protocol">
          <uap:Protocol Name="myapp">
              <uap:DisplayName>MauiAuth0App</uap:DisplayName>
          </uap:Protocol>
          </uap:Extension>
      </Extensions>
      <!-- 👆 new code -->
    </Application>
  </Applications>

  <!-- ...existing markup... -->
  
</Package>

运行你的MAUI应用程序

现在你可以使用Auth0进行认证来运行你的.NET MAUI应用程序。根据你的开发环境,你可以使用物理设备或模拟器运行你的目标平台应用程序。用户体验将是相似的。

例如,在Mac上用前面讨论的方法之一启动应用程序后,你会得到以下屏幕。

The .NET MAUI application with the login button

点击登录按钮,出现一个警告对话框,通知你正在前往Auth0进行认证。

Warning dialog for the MAUI app

点击继续按钮,一个浏览器实例打开,显示Auth0通用登录页面。如果你在你的Auth0租户上已经有一个用户,你可以使用这个页面进行认证,或者注册应用程序。

The Auth0 Universal Login page

认证后,你将访问你在添加认证支持之前得到的主屏幕。

Running .NET MAUI app

当你点击 "*点击我 "*按钮时,你可能发现这个页面没有反应。这看起来像是一个错误,尽管我在项目的GitHub仓库中没有发现任何参考资料。你可以通过稍微调整应用程序的窗口大小来解决这个问题。

现在你的MAUI应用程序只允许已认证的用户访问。

显示用户资料

你可能希望你的MAUI应用程序的一个功能是能够显示用户的个人资料,如他们的名字和照片。你的应用程序已经有了这些数据,因为Auth0客户端已经被配置了适当的OpenID Connect作用域,即请求获得关于用户的特定数据。为了访问和显示这些数据,你需要对应用程序的主页面做一些修改。

打开该 MainPage.xaml文件,并添加下面强调的标记。

<!-- MainPage.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiAuth0App.MainPage">
             
    <ScrollView>
        <VerticalStackLayout 
            Spacing="25" 
            Padding="30,0" 
            VerticalOptions="Center">          
          <StackLayout x:Name="LoginView">
            
            <!-- ...existing markup... -->
            
          </StackLayout>

          <StackLayout x:Name="HomeView"
                       IsVisible="false">

            <!-- ...existing markup... -->
            
            <!-- 👇 new code -->
            <Image
                x:Name="UserPictureImg"
                Source=""
                SemanticProperties.Description="User's picture"
                HeightRequest="200"
                HorizontalOptions="Center" />

            <Label 
                x:Name="UsernameLbl"
                Text=""
                SemanticProperties.HeadingLevel="Level2"
                SemanticProperties.Description="User's name"
                FontSize="18"
                HorizontalOptions="Center" />
            <!-- 👆 new code -->
            
          </StackLayout>
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

你添加两个新元素到 <StackLayout>元素,名为HomeView

  • 一个名为UserPictureImg 的图像元素,它将指向用户的图片。
  • 一个名为UsernameLbl 的标签元素,它将显示用户的名字。

要把当前用户的数据分配给这些元素,请打开 MainPage.xaml.cs文件并应用以下修改。

// MainPage.xaml.cs

using MauiAuth0App.Auth0;

namespace MauiAuth0App;

public partial class MainPage : ContentPage
{
  // ...existing code...
  
  private async void OnLoginClicked(object sender, EventArgs e)
  {
    var loginResult = await auth0Client.LoginAsync();

    if (!loginResult.IsError)
    {
      // 👇 new code
      UsernameLbl.Text = loginResult.User.Identity.Name;
      UserPictureImg.Source = loginResult.User
        .Claims.FirstOrDefault(c => c.Type == "picture")?.Value;
      // 👆 new code
      
      LoginView.IsVisible = false;
      HomeView.IsVisible = true;
    }
    else
    {
      await DisplayAlert("Error", loginResult.ErrorDescription, "OK");
    }
  }
}

添加的代码利用了存储在loginResult 变量中的认证过程的结果。具体来说,它访问了User 属性,这使你可以得到用户的资料数据。你可以看到,用户的名字是直接从 User.Identity.Name属性中提取,而图片则是从 User.Claims集合。这个集合包含了由Auth0返回的关于当前用户的所有声明

这就是全部!一旦你运行你的应用程序并进行认证,你将得到一个类似于以下的屏幕。

MAUI app with user profile

添加注销

到目前为止,建立的应用程序允许用户登录,但不允许退出。让我们来实现这个功能。

首先,打开 Auth0Client.cs``Auth0 文件,并添加 LogoutAsync()方法,如下图所示。

// Auth0/Auth0Client.cs

using IdentityModel.OidcClient;
using IdentityModel.OidcClient.Browser;  // 👈 new code
using IdentityModel.Client;              // 👈 new code

namespace MauiAuth0App.Auth0;

public class Auth0Client
{
  private readonly OidcClient oidcClient;
  
  // ...existing code...
  
  // 👇 new code
  public async Task<BrowserResult> LogoutAsync() {
    var logoutParameters = new Dictionary<string,string>
    {
      {"client_id", oidcClient.Options.ClientId },
      {"returnTo", oidcClient.Options.RedirectUri }
    };

    var logoutRequest = new LogoutRequest();
    var endSessionUrl = new RequestUrl($"{oidcClient.Options.Authority}/v2/logout")
      .Create(new Parameters(logoutParameters));
    var browserOptions = new BrowserOptions(endSessionUrl, oidcClient.Options.RedirectUri) 
    {
        Timeout = TimeSpan.FromSeconds(logoutRequest.BrowserTimeout),
        DisplayMode = logoutRequest.BrowserDisplayMode
    };

    var browserResult = await oidcClient.Options.Browser.InvokeAsync(browserOptions);

    return browserResult;
  }
  // 👆 new code
}

这里有两个新的命名空间引用和 LogoutAsync()方法定义。这个方法建立了注销端点的URL,并使用配置的浏览器调用它。

现在,前往 MainPage.xaml文件,添加注销按钮,如下图所示。

<!-- MainPage.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiAuth0App.MainPage">
             
    <ScrollView>
            
            <!-- ...existing markup... -->
            
          <StackLayout x:Name="HomeView"
                       IsVisible="false">

            <!-- ...existing markup... -->
            
            <!-- 👇 new code -->
            <Button 
                x:Name="LogoutBtn"
                Text="Log Out"
                SemanticProperties.Hint="Click to log out"
                Clicked="OnLogoutClicked"
                HorizontalOptions="Center" />
            <!-- 👆 new code -->
            
          </StackLayout>
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

这是一个名为LogoutBtn 的按钮元素,在点击时调用 OnLogoutClicked()方法。

让我们来实现 OnLogoutClicked()方法在 MainPage.xaml.cs文件中实现这个方法。

// MainPage.xaml.cs

using MauiAuth0App.Auth0;

namespace MauiAuth0App;

public partial class MainPage : ContentPage
{
  // ...existing code...
  
  // 👇 new code
  private async void OnLogoutClicked(object sender, EventArgs e)
  {
    var logoutResult = await auth0Client.LogoutAsync();

    if (!logoutResult.IsError) {
        HomeView.IsVisible = false;
        LoginView.IsVisible = true;
    } else {
        await DisplayAlert("Error", logoutResult.ErrorDescription, "OK");
    }
  }
  // 👆 new code
}

OnLogoutClicked()方法调用 LogoutAsync()该方法调用Auth0客户端的如果没有错误,当前视图被隐藏,并显示登录视图。否则,会显示一个信息错误。

现在你的应用程序有一个注销按钮。

MAUI app with the logout button

使用WebView

在结束本文之前,让我们看看如何解决前面提到的Windows和WebAuthenticator 的问题。你可以通过使用WebView而不是外部浏览器来解决这个问题。

在桌面应用程序中使用WebViews和外部浏览器是一个备受争议的话题。OAuth2安全最佳实践建议对所有本地应用程序使用外部浏览器。然而,并不是每个人都同意这些最佳实践,因为桌面应用程序使用外部浏览器有一些可用性和技术问题。虽然很有趣,但这个讨论不在本文的范围内。

移动到Auth0 文件夹中,添加一个名为 WebViewBrowserAuthenticator.cs.把下面的代码放在该文件中。

// Auth0/WebViewBrowser.cs

using IdentityModel.OidcClient.Browser;

namespace MauiAuth0App;

public class WebViewBrowserAuthenticator: IdentityModel.OidcClient.Browser.IBrowser
{
  private readonly WebView _webView;

  public WebViewBrowserAuthenticator(WebView webView)
  {
    _webView = webView;
  }

  public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
  {
    var tcs = new TaskCompletionSource<BrowserResult>();

    _webView.Navigated += (sender, e) =>
    {
      if (e.Url.StartsWith(options.EndUrl))
      {
        _webView.WidthRequest = 0;
        _webView.HeightRequest = 0;
        tcs.SetResult(new BrowserResult { ResultType = BrowserResultType.Success, Response = e.Url.ToString() });
      }

    };

    _webView.WidthRequest = 600;
    _webView.HeightRequest = 600;
    _webView.Source = new UrlWebViewSource { Url = options.StartUrl };

    return await tcs.Task;
  }
}

WebViewBrowserAuthenticator 类实现了IBrowser 接口,但使用传递给构造函数的WebView作为参数,而不是WebAuthenticator 对象。

在该 MainPage.xaml文件中,添加一个WebView元素,如下图所示。

<!-- MainPage.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiAuth0App.MainPage">
             
    <ScrollView>
        <VerticalStackLayout            
           Spacing="25" 
           Padding="30,0" 
           VerticalOptions="Center">
           <StackLayout
               x:Name="LoginView">
               <Button 
                   x:Name="LoginBtn"
                   Text="Log In"
                   SemanticProperties.Hint="Click to log in"
                   Clicked="OnLoginClicked"
                   HorizontalOptions="Center" />

               <!-- 👇 new code -->
               <WebView x:Name="WebViewInstance" />
               <!-- 👆 new code -->
            </StackLayout>

            <!-- ...existing markup... -->
           
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

名字为WebViewInstance 的WebView元素被添加到LoginView <StackLayout>元素。

现在让我们使WebView只在Windows平台上操作。打开该 MainPage.xaml.cs文件,添加以下几行代码。

// MainPage.xaml.cs

using MauiAuth0App.Auth0;

namespace MauiAuth0App;

public partial class MainPage : ContentPage
{
    int count = 0;
    private readonly Auth0Client auth0Client;
  
    public MainPage(Auth0Client client)
    {
        InitializeComponent();
        auth0Client = client;

    // 👇 new code
      #if WINDOWS
        auth0Client.Browser = new WebViewBrowserAuthenticator(WebViewInstance);
        #endif
    // 👆 new code
  }

  // ...existing code...
  
}

你注意到这里有一个条件编译语句,只适用于Windows目标平台。在这种情况下,Auth0客户端的Browser 属性将被分配给一个WebViewBrowserAuthenticator 类的实例,该类使用我们刚刚添加到用户界面的WebView。

最后的修改适用于 MauiProgram.cs文件。你需要对回调URI进行自定义,如图所示。

// MauiProgram.cs

using MauiAuth0App.Auth0;

namespace MauiAuth0App;

public static class MauiProgram
{
  public static MauiApp CreateMauiApp()
  {

    // ...existing code...
    
    builder.Services.AddSingleton(new Auth0Client(new()
    {
      Domain = "<YOUR_AUTH0_DOMAIN>"
      ClientId = "<YOUR_CLIENT_ID>"
      Scope = "openid profile",
      // 👇 new code
            #if WINDOWS
            RedirectUri = "http://localhost/callback"
            #else
      RedirectUri = "myapp://callback"
            #endif
      // 👆 new code
    }));

    return builder.Build();
  }
}

RedirectUri 属性的原始赋值已被另一个条件编译语句所取代。当Windows是目标平台时,回调URI将是 http://localhost/callback而不是 myapp://callback.之所以需要这样做,是因为WebView运行在你的MAUI应用程序中,所以不需要定制方案,使浏览器在用户认证后调用你的应用程序。重定向发生在WebView中,你可以控制它。

由于你使用的是不同的回调URI,你需要将 http://localhost/callback值添加到Auth0仪表板上的应用程序设置的允许回调URLs允许注销URLs字段。

这些变化让Auth0认证也能在Windows上为你的.NET MAUI应用工作。你将看到Auth0通用登录页面嵌入到你的应用程序中,而不是在一个外部浏览器中,如下图所示。

MAUI app on Windows using a WebView

总结

在本文结束时,你已经掌握了将Auth0认证添加到你的.NET MAUI应用程序的基本知识。

你学会了如何在OpenID Connect客户端库的基础上创建Auth0客户端组件,如何定义启动外部浏览器进行用户认证的类,以及如何修改用户界面以允许只对认证用户进行访问。然后,你看到了如何添加特定平台的设置,以允许外部浏览器在用户认证后调用你的应用程序,最后,如何为特定平台运行你的应用程序。

在这第一步之后,你在应用程序的用户界面上显示了用户的名字和照片,并实现了注销功能。

最后,你了解到,一个已知的问题使你的MAUI应用程序无法在Windows平台上使用外部浏览器对用户进行认证。为了解决这个问题,你实现了基于WebView的用户认证。

在整个文章中,你了解到MAUI框架仍然有一些问题需要注意。希望它们能在不久的将来得到解决,这样就不再需要一些变通方法了。

你可以从这个GitHub仓库下载本文中所构建的项目的完整代码。


© 2013-2022 Auth0公司。保留所有权利。