在iOS应用程序中使用Auth0用户和应用程序元数据

473 阅读23分钟

在学习了如何在基于 UIKitSwiftUI的iOS应用中添加认证后,你可能想存储关于用户的额外信息。你可以通过访问 元数据在Auth0的用户配置文件中,你将在本教程中学习如何做到这一点。

如果你不熟悉将Auth0认证与iOS应用程序集成,我们强烈建议你首先通过以下练习 开始使用Swift和UIKit进行iOS认证开始使用SwiftUI进行iOS认证.

如果你想在关注构建和执行步骤的同时略过内容,请寻找🛠表情符号。利用control-F/command-F的力量,快速构建本教程中的应用

用户元数据和应用程序元数据

Auth0的用户档案存储了每个用户的基本信息:一个独特的标识符,他们使用的名字,他们的联系细节,以及其他识别信息,如他们的照片。在认证过程中,这些信息很有用,但在用户登录时,你可能经常需要其他信息,比如:

  • 用户的状态:用户是在试用的基础上使用该应用程序,还是付费用户?用户是否可以使用应用程序的基本功能,或者他们是否购买了具有所有功能的豪华包?
  • 用户的偏好,如喜欢的颜色方案、浅色模式与深色模式、字体大小等等。
  • 追踪用户所采取的行动的标志。用户是否接受了应用程序的条款和条件?他们是否已经完成了教程?
  • 关于用户账户的其他信息。是时候向用户显示一个警报、公告或通知了吗?应用程序是否有可用的升级?

Auth0的用户档案可以将这类信息作为元数据存储,一旦用户登录,你的应用程序就可以访问这些信息。这是你的应用程序可以保留在其存储系统中的信息,但Auth0的用户资料元数据的优点是在登录后和登录过程中立即可用。

元数据的一个特别优势是,它与用户的账户一起存储,而不是在设备上。如果用户从一个应用程序的移动版本切换到其网络版本(反之亦然),他们基于元数据的设置和偏好将在两个版本中是相同的。

更好的是,Auth0的Actions--你可以添加到你的租户中的基于Node的自定义代码--可以使用你的用户的元数据在许多Auth0工作流程中执行任务,例如当他们登录、注册账户或更改密码时。

为了让你更好地了解你的应用程序可以用元数据做什么,你将在本文的练习中制作的这个应用程序使用两种元数据。

  • **用户元数据,**用户可以看到并更新。它是用来存储用户的偏好、设置和其他影响用户可用的核心功能的数据。
  • **应用元数据,**用户不能看到,但可以被应用访问。它用于存储设置和其他确实影响应用程序核心功能的数据,如跟踪用户活动和确定哪些功能或活动应提供给用户。

在本教程中,你将为一个 "入门 "应用程序添加元数据功能,让用户查看和更新他们的用户元数据,并在用户的应用程序元数据包含一个特定值时显示一个公告。

元数据演示应用程序

让我们来看看你在这个练习中要做的应用吧

当你启动该应用时,初始屏幕上会出现该应用的标题、元数据演示和一个登录按钮来迎接你。

Opening screen, with “Metadata demo” in title font and “Log in” button below it.

注意,登录按钮看起来像一个按钮,而不是蓝色的文字。iOS 15引入了四种预定义的按钮样式,其中三种提供了一个背景,让用户知道这确实是一个按钮。应用程序中的按钮使用的是填充式按钮样式。

按下 "登录"按钮,就可以登录到该应用程序。你会看到的第一件事是这个提示框。

“‘iOS SwiftUI Metadata’ Wants to Use ‘auth0.com’ to Sign In” alert box.

这个提示框是iOS内置的一个隐私功能。你看到它是因为该应用即将从Auth0接收关于用户--你--的信息,作为登录过程的一部分。这些信息将包括关于你的识别信息,如你的姓名、电子邮件地址和你的照片的URL。它还将包括Auth0所存储的关于你的额外元数据。

iOS的隐私政策要求它在应用程序从第三方(本例中为Auth0)发送或接收有关他们的个人信息时通知用户,它通过警报框做到这一点。

有一种方法可以禁用登录提示框,但这超出了本文的范围。更多细节,请参见 Auth0.swift库的FAQ中的如何禁用登录提示框。

按 "继续"按钮。这将带你到Auth0通用登录屏幕,它出现在嵌入应用程序的网络浏览器视图中。

Universal Login screen, part 1: Entering the email address.

当你使用Auth0为你的应用程序添加登录/注销功能时,你将认证委托给一个由Auth0托管的登录页面。如果你使用谷歌的网络应用,如Gmail和YouTube,你已经看到了这个动作。这些服务重定向你使用accounts.google.com来登录。登录后,谷歌会将你作为一个已登录的用户返回到网络应用中。

如果你担心使用Auth0的 "通用登录 "意味着你的应用程序的登录屏幕将停留在默认的Auth0 "外观和感觉 "上,我有个好消息要告诉你:你可以定制它以符合你的应用程序或组织的品牌形象。

通用登录页面使你不必为自己的认证系统编码。它为你的应用程序提供了一个自成一体的登录框,具有多项功能,以提供一个良好的用户体验。

一旦你输入了你的名字,你将进入通用登录页面的第二部分,在那里你将输入你的密码。

Universal Login screen, part 2: Entering the password.

你将进入这个屏幕,它让你选择通过使用生物识别技术--你的脸或你的指纹--而不是输入密码来更快地登录。目前,请选择现在不需要

Universal Login screen, part 3: “Log In Faster on This Device” screen.

每当用户第一次使用 "通用登录 "来验证一个应用程序时,他们会看到这个屏幕,它要求允许使用他们的用户账户信息。

Universal Login screen, part 4: “Authorize App” screen.

以下是该应用要求允许访问的内容:

  • 简介:关于用户的基本信息:
    • 用户的全名、电子邮件地址和照片,应用程序将在用户登录时显示这些信息。
    • 用户的唯一标识符。该应用程序将使用它来请求以下项目。
  • 当前用户:关于用户的额外信息,包括额外的名字(名、姓、昵称等),额外的联系信息(电话号码),以及他们的电子邮件地址是否经过验证。应用程序不会使用这些信息,但需要访问这些数据的授权,以便访问用户元数据。
  • 当前的用户元数据(Currentusermetadata):这些是附着在用户账户上的用户元数据和应用程序元数据。应用程序将使用用户元数据来存储用户可以编辑的用户偏好,并使用应用程序元数据来确定链接的内容和URL以及是否应该显示该链接。

按下接受按钮,继续。这就完成了登录过程,带你到应用程序的主屏幕,显示用户账户的信息。

“You’re logged in!” screen for user “randomuser@example.com”, showing the user’s name, email, and empty “personal affirmation” text field.

正如你所看到的,该应用程序显示你的姓名、电子邮件地址,以及你创建账户时自动生成的照片。

它还显示你的个人申明--用户元数据--你可以编辑。如果你还没有输入肯定语,文本字段将是空的。如果你有,当应用程序启动时,以及每当你按下刷新申明按钮时,它将出现在文本领域。

“You’re logged in!” screen for user “randomuser@example.com”, showing the user’s name, email, and filled-in “personal affirmation” text field.

该应用程序还使用应用程序元数据来确定它是否应该显示 "公告 "网络链接。应用程序元数据还决定了链接的文本和URL。

“You’re logged in!” screen for user “skippy@example.com”, showing the user’s name, email, filled-in “personal affirmation” text field, and “Tap here for an important announcement” link.

如果你点击 "点此查看重要公告"链接,应用程序会打开一个包含该公告的YouTube视频,该视频由1980年代的流行歌星里克-阿斯特利(Rick Astley)发布。

Rick Astley’s “Never Gonna Give You Up” video on YouTube.

如果你选择按下注销按钮,该应用就会启动注销程序,开始时,iOS的提示框会再次告诉你,该应用正在使用Auth0登录,即使你正在注销。这是因为无论你是登录还是退出,iOS都会显示相同的提示框;我们希望苹果有一天能纠正这一点。

“‘iOS SwiftUI Metadata’ Wants to Use ‘auth0.com’ to Sign In” alert box.

也有一种方法可以禁用注销提示框。与登录提示框一样,禁用它超出了本文的范围。更多细节,请参阅 Auth0.swift库的FAQ中的如何禁用注销提示框。

在你按下警报框上的 "继续"按钮后,你会回到应用程序的初始屏幕,现在显示 "你已注销 "作为标题文本。

“Logged out” screen, with “You’re logged out.” in title font and “Log in” button below it.

先决条件

你需要以下条件来构建这个应用程序。

1.一个Auth0账户

该应用程序使用Auth0来验证用户,这意味着你需要一个Auth0账户。你可以注册一个免费账户,它可以让你在10个应用程序中添加登录/注销,支持7000个用户和无限的登录。这应该适合你的原型设计、开发和测试需要。

2.一个iOS/iPadOS开发设置

  • 任何2013年或以后的Mac电脑--MacBook、MacBook Air、MacBook Pro、iMac、iMac Pro、Mac Mini、Mac Pro或Mac Studio,至少有8GB内存。当涉及到内存时,一般来说越多越好。
  • 苹果的开发者工具,Xcode 11.0版(2019年9月)或更高版本。写这篇文章时,我使用的是当时的版本:13.4.1(build 13F100),2022年6月2日发布。

3.一个iOS设备,虚拟的或真实的

Xcode自带的Simulator应用程序,可以模拟最近的iPhone、iPad和iPod Touch模型。Xcode 13.4的虚拟设备默认运行iOS 15.5。

要为模拟器安装早期版本的iOS,从Xcode菜单中选择Preferences...,然后选择Components标签。你会看到一个旧版本的列表,不仅仅是iOS,还有watchOS和tvOS。

使用模拟器的好处之一是,你不需要一个免费或付费的苹果开发者账户来使用它。

虽然模拟器很方便,但它不能替代实际的物理设备。它提供了更真实的测试体验,而且有一些事情你不能在模拟器上做(如运动/倾斜感应和增强现实)。

你需要一个免费的苹果开发者账户来直接将应用部署到设备上进行测试(你需要付费的账户来将应用部署到App Store),你需要在你的开发者账户中注册设备来将应用部署到它。更多细节,请参阅苹果的文章。 将你的应用程序分发到注册设备上。

要了解更多关于在虚拟和真实的iOS设备上运行应用程序的信息,请从苹果的 在模拟器或设备上运行你的应用程序。

第一个步骤

下载启动项目

为了使本教程专注于在基于SwiftUI的iOS应用中为基于Auth0的认证添加用户和应用元数据支持,我创建了一个启动项目,你可以下载。这个应用允许用户登录,看到他们的名字、电子邮件地址和照片,然后退出。使用这个启动项目,你将能够专注于为应用程序添加基于用户和应用程序元数据的功能,而不必为从头开始构建它而分心。

🛠下载包含应用程序的启动和完成项目的.zip文件(36 KB)并解压。这将在你的本地驱动器上创建一个 ios-swiftui-metadata-main在你的本地驱动器上创建一个文件夹。

🛠 打开该 ios-swiftui-metadata-main文件夹,并寻找 iOS SwiftUI Metadata (starter)文件夹。打开该文件夹,然后打开启动器项目文件。 iOS SwiftUI Metadata.xcodeproj.Xcode将花一点时间(取决于你的网络连接速度)为项目下载Auth0包的依赖。

**先不要运行这个项目!**除非你在Auth0中注册了该应用,否则它不会工作,你很快就会这样做。

探索启动项目

初始项目已经包含了Auth0软件包的依赖项。Auth0 JWTDecodeSimpleKeychain 。你不需要安装任何包。

如果你正在从头开始构建一个iOS项目,并希望纳入Auth0,你需要将Auth0.swift包添加到项目中。你可以通过选择添加包......并在 "搜索 "文本字段中输入 Auth0.swift在出现的窗口中的 "搜索 "文本字段中输入Auth0.swift。从依赖关系规则菜单中选择Up to Next Minor Version,然后点击软件包列表中的Auth0.swift图标来安装它。

虽然启动项目和其他iOS项目一样,有很多文件,但你只需要编辑其中的两个文件。

  • **ContentView.swift:**应用程序的唯一视图,它在 "注销 "和 "登录 "模式下都显示唯一的屏幕。
  • **Auth0.plist:**一个专门用于保存Auth0相关信息的属性列表,即你的租户的域名和应用程序的客户ID。

你可能想探索这些文件,它们包含了 "助手 "和实用对象及函数。

  • **Profile.swift:**一个用于从ID Token中提取和存储用户信息的结构。
  • **UserImage.swift:**一个视图,用于从指定的URL下载和显示图片。它在下载过程中显示一个占位符图像。
  • **Utilities.swift:**一个放置杂项实用功能的地方。它包含了 auth0Plist()函数,它将 Auth0.plist文件的内容,作为一个Dictionary 对象。

在Auth0仪表盘中注册入门级应用程序

现在你有了Starter项目,是时候向Auth0注册其应用程序了。

🚨**注意:你需要一个Auth0账户才能继续进行这一步。**🚨 再一次,你可以免费注册一个账户

🛠 在Auth0仪表板上,点击左侧菜单中的应用程序

Auth0 dashboard’s “Getting Started” page, with instructions to expand the “Applications” menu.

🛠 这将展开应用程序菜单。选择该菜单中的第一个项目,它也有一个名字叫 "应用程序"。

Auth0 dashboard’s “Getting Started” page, with instructions to select the “Applications” menu item.

现在你将进入 "应用程序"页面。它列出了所有你已经注册的使用Auth0进行认证和授权的应用程序。

🛠 注册应用程序。通过点击页面右上方的创建应用程序按钮来完成。

Auth0 dashboard’s “Applications” page, with instructions to click the “Create Application” button.

会出现这个对话框。

“Create Application’ dialog.

🛠 执行以下操作以继续。

  • 在 "名称"字段中输入一个应用程序的名称。最简单的方法是使用与你的Xcode项目相同的名字,iOS SwiftUI Metadata.
  • 指定应用程序的类型,在本例中是Native

🛠 点击创建。该应用的快速启动页面将会出现。单击 "设置"选项卡。

The “Settings” page for the iOS SwiftUI Metadata applications, with instructions to click on the “Settings” tab.

当你在 "设置"页面的顶部时,执行以下操作。

🛠 复制字段的内容。

🛠 切换到 Xcode,打开Auth0 属性列表,将你刚才复制的值粘贴到Domain 行的Value列中。

🛠 切换回设置页面,复制客户端ID字段的内容。

🛠 切换到 Xcode 中的Auth0 属性列表,将你刚才复制的值粘贴到ClientId 行的Value列中。

The starter project in Xcode, with Auth0.plist in the editing view. There are instructions to paste the “Domain” and “Client ID” values from the dashboard into their corresponding cells here.

🛠 切换回设置页面,向下滚动到应用程序URIs部分。你需要构建一个URL,粘贴到允许回调的URLs允许注销的URLs字段。

The app’s “Settings” page in the Auth0 dashboard with the instructions “You need to fill these fields” for the “Allowed Callback URLs” and “Allowed Logout URLs” text values.

🛠 开始构建URL,将以下内容粘贴到允许的回调URL允许的注销URL字段。

{BUNDLE_IDENTIFIER}://{YOUR_DOMAIN}/ios/{BUNDLE_IDENTIFIER}/callback

🛠 将 {BUNDLE_IDENTIFIER}Allowed Callback URLsAllowed Logout URLs两个字段中用应用程序的捆绑标识符替换。如果你没有改变启动项目中的捆绑标识符,这个值是 com.auth0.iOS-SwiftUI-Metadata.注意 {BUNDLE_IDENTIFIER}在URL中出现了两次;你需要替换它两次。

🛠 在允许回调的URLs允许注销的URLs字段中,用你的域名字段中的值替换。 {YOUR_DOMAIN}替换为你在本页面前面看到的字段的值。

🛠 向下滚动到页面底部,单击保存更改按钮。

The bottom of the app’s “Settings” page in the Auth0 dashboard with  instructions to click the “Save” button.

创建一个新用户

这个练习在具有特定用户和应用程序元数据的用户中效果最好,所以我们来创建一个。

🛠 在Auth0仪表板左侧的菜单中,点击用户管理

The Auth0 dashboard, with instructions to expand the “User Management” menu.

🛠 这将展开用户管理菜单。选择该菜单中的 "用户"项目。

The Auth0 dashboard, with instructions to select the “Users” menu item.

将出现 "用户"页面。它列出了所有注册到你的租户的用户。如果没有用户,你会看到 "你还没有任何用户 "的信息。

The “Users” page with instructions to click the “Create User” button.

🛠 单击 "创建用户"按钮来创建一个新用户,这将使这个对话框出现。

The “Create User” dialog.

🛠 为用户输入一个电子邮件地址和密码。连接的唯一选项将是用户名-密码-认证,所以保持原样。记下这个电子邮件地址和密码--你将用它们来登录应用程序。

🛠 点击创建按钮来创建用户。该用户的详细信息页面将出现。

The user’s “Details” page.

🛠 向下滚动到元数据部分。

The “Metadata” section of the user page with two large blank text areas labeled “user_metadata” and “app_metadata”.

🛠 将这个JSON粘贴到user/metadata_文本区。

{
  "personal_affirmation": "Believe in yourself!"
}

你将更新应用程序以接收这个JSON,提取personal_affirmation ,并在一个文本字段中显示它。你还将赋予应用程序更新这个值的能力,你将能够通过检查这里的值来确认更新是否成功。

🛠 将这个JSON粘贴到app/metadata_文本区域。

{
  "display_announcement": true,
  "announcement_text": "Tap here for an important announcement",
  "announcement_url": "https://www.youtube.com/watch?v=DLzxrzFCyOs"
}

你将更新应用程序来接收这个JSON,提取display_announcement,announcement_text, 和announcement_url 的值,并使用它们来显示一个带有指定文本和URL的链接,如果display_announcement's value is true.你可以通过改变这些值来改变链接和它是否出现。

🛠 点击appmetadata_文本区下面的保存按钮,保存你刚刚输入的元数据。

在你的浏览器中保持仪表盘窗口或标签打开;你以后会用它来检查用户的元数据值。

运行启动项目

🛠 确认启动项目运行。从设备菜单中选择一个模拟器或设备,运行该应用,并登录。

登录后,应用程序应该显示以下内容。

The app’s “Logged in” screen, displaying the user’s photo, name, and email address, as well as the “Log out” button.

启动项目的应用程序只是显示用户的姓名、电子邮件地址和照片。

看一下ID令牌、访问令牌和作用域

在你开始更新启动程序以使用用户的元数据之前,让我们快速看一下访问该元数据所需的几个东西。

🛠更新该 login()方法,如下所示。

  private func login() {
    Auth0
      .webAuth()
      .start { result in
        
        switch result {
          
          case .failure(let error):
          print("Failed to log in: \(error)")
          
          case .success(let credentials):
            self.isAuthenticated = true
            self.isJustLaunched = false
            self.userProfile = Profile.from(credentials.idToken)
            // New code 👇🏽
            print("ID Token: \(credentials.idToken)")
            print("Access Token: \(credentials.accessToken)")
            print("Scopes: \(credentials.scope!)")
            // New code 👆🏽
          
        } // switch
        
      } // start()
  }

🛠 运行应用程序,登录,并查看Xcode的输出控制台。最近的几行将包括以下内容。

ID Token: {a big string of 1000+ characters}
Access Token: {a big string of 400+ characters}
Scopes: openid profile email

当用户成功登录时,Auth0返回一个包含若干属性的Credentials 实例。这些 print()语句,你刚刚添加到 login()显示了三个属性的值,你需要用它们来访问用户的元数据。

ID Token icon

**ID Token。**这是用户被认证的证明。它看起来像一个超过1000个随机字符的字符串,但它实际上是一个经过64进制编码的JSON网络令牌(简称JWT)。

ID令牌有识别用户的信息被编码在里面。这包括用户的姓名、电子邮件地址和他们的照片的URL;启动程序在用户登录时显示这些信息。

ID令牌还包含了用户的唯一标识符,你将把它与访问令牌结合起来使用,以便从Auth0请求用户的元数据。

Access Token icon

**访问令牌。**这包含了应用程序代表用户执行特定操作的授权。这些授权在scope 属性中(见下面的作用域)。和ID Token一样,Access Token看起来是一串随机字符,但没有那么长(400多个字符,而ID Token是1000多个)。

你将使用访问令牌与来自ID令牌的用户唯一标识符来获取元数据。

你可以在我们的文章中找到更多关于ID和访问令牌的信息,ID令牌和访问令牌。有什么区别?

Scope icon

**作用域。**这些在访问令牌中指定的授权是由作用域定义的,它定义了应用程序可以代表用户执行的具体行动。scope 属性包含授予应用程序的作用域,作为一组以空格分隔的字符串。目前授予该应用程序的作用域是。

  • openid:授权使用OpenID连接来验证用户的身份。这个作用域是必需的;没有它,你就不能使用Auth0来验证用户。
  • profile:授权访问用户资料中的基本信息。这最常被用来访问用户的名字。
  • email:授权访问用户的电子邮件地址。

如果应用程序没有明确请求作用域,这些是授予它的默认作用域。为了访问用户资料中的元数据,应用程序需要请求一些额外的作用域。

由于作用域是对应用程序代表用户做事的授权,应用程序必须宣布用户正在这样做,并请求用户的批准。这就是为什么当用户第一次登录一个应用程序时,通用登录会显示授权应用程序的屏幕。

如果你是认证的新手(或者只是需要复习一下),作用域和它们的表亲--权限和特权--可能令人困惑。幸运的是,我们的常规系列文章《困惑的开发者》在本文中使它们变得容易理解。 权限、特权和范围.

个人申明 "部分

现在是时候对应用程序进行第一次修改,实现应用程序的 "个人肯定 "部分。由于用户应该能够访问他们的个人申明,它将被存储为用户元数据,Auth0.swift库可以从中读取和写入。

更新ContentView"的属性

🛠 更新ContentView的属性,位于其开始附近,如下所示。

// [ 📄 ContentView.swift ]

// [ More code here ]

struct ContentView: View {
  
  @State private var userProfile = Profile.empty
  // New code 👇🏽👇🏽👇🏽 
  @State private var accessToken: String = ""
  @State private var cachedUserMetadata: [String : Any]? = nil
  // New code 👆🏽👆🏽👆🏽

  @State private var isAuthenticated = false
  @State private var isJustLaunched = true
  // New code 👇🏽👇🏽👇🏽
  @State private var personalAffirmation = ""
  // New code 👆🏽👆🏽👆🏽
  
  var body: some View {

// [ More code here ]

你刚刚给ContentView 添加了三个属性。

  1. accessToken:一个包含用户访问令牌的字符串。应用程序必须提供访问令牌和用户的ID来检索或更新用户的元数据。
  2. cachedUserMetadata:一个包含用户的用户元数据的字典。你很快就会写方法来检索和更新这个元数据。
  3. personalAffirmation:用户的个人申明,从cachedUserMetadata 。我们要给它一个自己的@State 变量,这样它的值就可以被绑定到一个文本字段。

添加 "个人申明 "部分的屏幕控件

🛠 添加绘制用户界面中 "个人肯定 "部分的代码。通过更新ContentView'sbody 属性来做到这一点。

// [ 📄 ContentView.swift ]

// [ More code here ]

  var body: some View {
      
    if isAuthenticated {
      
      // “Logged in” screen
      // ------------------
      
      ScrollView {
        
        VStack {
          
          Text("You’re logged in!")
            .modifier(TitleStyle())
          
          VStack {
            UserImage(urlString: userProfile.picture)
            Text("Name: \(userProfile.name)")
            Text("Email: \(userProfile.email)")
          }
          .padding()
          
          // New code 👇🏽👇🏽👇🏽
          VStack { // Affirmation section
            
            // Affirmation label and text field
            Text("Your personal affirmation:")
            TextField("Your affirmation here", text: $personalAffirmation)
              .modifier(TextFieldStyle())
            
            HStack { // Affirmation button row
              Spacer()
              Button("Refresh affirmation") {
                getMetadata()
              }
              Spacer()
              Button("Save affirmation") {
                updateUserMetadata()
              }
              Spacer()
            } // HStack - Affirmation button row
            
          } // VStack - Affirmation section
          // New code 👆🏽👆🏽👆🏽
          
          Button("Log out") {
            logout()
          }
          .buttonStyle(BigButtonStyle())
          
        } // VStack
        
      } // ScrollView

    } else {
  
// [ More code here ]

你刚刚输入的代码在应用程序的视图中添加了一个VStack ,包含以下内容。

  • 一个显示用户个人申明的文本字段和一个文本字段的标签
  • 一个刷新申明的按钮,当按下时获得用户的用户元数据的最新版本
  • 一个保存申明按钮,将文本字段中的文本作为用户个人申明的值写入他们的用户元数据中。

更新 login()方法

🛠 对ContentView's的 login()方法进行更新,如下图所示。

// [ 📄 ContentView.swift ]

// [ More code here ]

  private func login() {
    // New code 1 👇🏽👇🏽👇🏽
    guard let domain = auth0Plist()?["Domain"] 
    else {
      return
    }
    // New code 1 👆🏽👆🏽👆🏽
    
    Auth0
      .webAuth()
      // New code 2 👇🏽👇🏽👇🏽
      .audience("https://\(domain)/api/v2/")
      .scope("openid profile email read:current_user update:current_user_metadata")
      // New code 2 👆🏽👆🏽👆🏽
      .start { result in
        
        switch result {
          
          case .failure(let error):
          print("Failed to log in: \(error)")
          
          case .success(let credentials):
            self.isAuthenticated = true
            self.isJustLaunched = false
            self.userProfile = Profile.from(credentials.idToken)
            // New code 3 👇🏽👇🏽👇🏽
            self.accessToken = credentials.accessToken
            self.getMetadata()
            // New code 3 👆🏽👆🏽👆🏽
            
            print("ID Token: \(credentials.idToken)")
            print("Access Token: \(credentials.accessToken)")
            print("Scopes: \(credentials.scope!)")
          
        } // switch
        
      } // start()
  }
  
// [ More code here ]

你在该方法的三个不同地方添加了新的代码。 login()方法中添加了新的代码,在三个不同的地方。让我们按顺序看一下。

  1. **新代码1:**这试图从Auth0 属性列表中检索你的租户的域名。如果成功,该 login()方法将继续执行;否则,应用程序将退出该 login()方法。我们需要域名来指定Auth0管理API的URL,我们用它来访问用户的元数据
  2. **新代码2:**为了处理用户的用户元数据,应用程序需要有访问Auth0管理API的授权。你添加到WebAuth 协议的授权链中的两个方法调用可以做到这一点。
    • 方法调用 audience()方法调用指定应用程序将使用访问令牌调用的API。它的参数是你应用程序租户的Auth0管理API的URL。
    • 方法调用 scope()方法调用指定应用程序正在请求的作用域。除了三个默认作用域外,它还增加了这两个。
      • read:current_user:读取用户的用户和应用程序元数据的授权
      • update:current_user_metadata:更新用户的用户元数据的授权
  3. **新代码3:**这段代码将credentials'accessToken 属性复制到ContentView'saccessToken 属性,使其可以在整个ContentView 。然后它调用 getMetadata()方法,你将在下一步中添加这个方法。

添加一个方法来获取用户的元数据

🛠 在 的方法后面添加一个新的方法 -- getMetadata()- 到ContentView ,就在 login()方法之后,在 logout()方法。

// [ 📄 ContentView.swift ]

// [ More code here ]

  func getMetadata() {
    // 1 
    if accessToken == "" {
      return
    }
    
    // 2
    Auth0
      .users(token: accessToken)
      .get(userProfile.id, fields: ["user_metadata", "app_metadata"])
      .start { result in
        
        switch result {
        
          // 3
          case .failure(let error):
            print("Error: Couldn’t retrieve metadata.\n\(error.localizedDescription)")
          
          // 4
          case .success(let metadata):
            // Get user metadata
            let userMetadata = metadata["user_metadata"] as? [String: Any]
            self.cachedUserMetadata = userMetadata
            self.personalAffirmation = cachedUserMetadata?["personal_affirmation"] as? String ?? ""
            
        } // switch
        
      } // start()
  }

// [ More code here ]

下面的注释与上述代码中的编号注释相对应。

  1. getMetadata()在继续之前,确认在ContentView'accessToken 属性中存在一个访问令牌。
  2. 这段代码通过在一个链中调用以下方法来启动读取用户元数据的请求。
    • users()使用访问令牌调用Auth0管理API。
    • get()提供用户的唯一标识符并指定我们要访问的用户信息:用户的用户元数据和应用程序元数据。
    • start()启动了检索元数据的过程。作为它的最终参数,它需要一个闭包来处理元数据检索的成功和失败情况。
  3. 如果应用程序不能检索到元数据,它只是向调试控制台打印一条错误信息。
  4. 如果应用程序成功地检索到了用户元数据,它就会将全套的用户元数据分配给ContentView'scachedUserMetadata 属性。它还将personal_affirmation 键的值分配给ContentView'spersonalAffirmation 属性。这就更新了 "个人申明 "文本字段的内容,因为它被绑定到该属性。

添加一个方法来更新用户的元数据

当用户按下 "保存申明"按钮时,应用程序应该用 "个人申明 "文本字段的内容更新用户的用户元数据。这个方法将做到这一点。

🛠 添加另一个方法--到 updateUserMetadata()- 到ContentView ,就在 getMetadata()方法之后,在 logout()方法。

// [ 📄 ContentView.swift ]

// [ More code here ]

  func updateUserMetadata() {
    // 1
    if accessToken == "" {
      return
    }
    
    // 2
    let updatedPersonalAffirmation = personalAffirmation.trimmingCharacters(in: .whitespacesAndNewlines)
    
    // 3
    Auth0
      .users(token: accessToken)
      .patch(userProfile.id, userMetadata: ["personal_affirmation": updatedPersonalAffirmation])
      .start { result in
        
        switch result {
          
          // 4
          case .failure(let error):
            print("Error: Couldn’t update 'personal_affirmation' in the user metadata.\n\(error.localizedDescription)")
          
          // 5
          case .success(let updatedUserMetadata):
            self.cachedUserMetadata = updatedUserMetadata
          
          } // switch
          
        } // start()
  }

// [ More code here ]

下面的注释与上面代码中的编号注释相对应。

  1. updateUserMetadata()确认在ContentView'saccessToken 属性中存在一个访问令牌,然后继续。
  2. 在用 "个人申明 "文本字段的内容更新用户的用户元数据之前,我们创建一个这些内容的副本,去掉任何前面和后面的空白。
  3. 这段代码通过调用链条中的以下方法来发起更新用户元数据的请求。
    • users()使用访问令牌调用Auth0管理API。
    • patch()提供用户的唯一标识符并指定要更新的用户元数据的键和值。
    • start()启动更新元数据的过程。作为它的最后一个参数,它需要一个闭包,处理用户元数据更新的成功和失败情况。
  4. 如果应用程序不能更新元数据,它只是向调试控制台打印一条错误信息。
  5. 如果应用程序成功地更新了用户元数据,它就会从Auth0接收更新的用户元数据,然后将其复制到ContentView'scachedUserMetadata 属性。

试试 "个人申明 "部分

🛠 运行应用程序,以你在本练习中早先创建的用户身份登录。

你会看到应用程序现在有一个 "个人申明 "部分,文本字段包含与你在用户的用户元数据中为用户输入的personal_affirmation 键相对应的值。

🛠 将个人肯定语改为不同的内容(如 "BELIEVE "或 "You've got this!!"),然后按保存肯定语按钮。

🛠 确认应用程序以两种不同的方式保存了新的肯定语。

  • 清除文本字段并按下刷新申明按钮。你新输入的申明应该重新出现。
  • 切换到Auth0仪表板,查看用户的user/metadata_部分。你应该在用户元数据JSON中看到你新输入的申明。

公告 "链接

现在你已经实现了应用程序的 "个人申明 "部分,现在是时候添加 "公告 "链接了。该应用程序只在必要时显示公告,而应用程序供应商决定这一点。这一决定是基于用户的信息,用户应该看到或改变这些信息,因此这些信息将被存储为应用程序元数据。Auth0.swift库可以从用户的应用程序元数据中读取,但不能写到它。

更新ContentView"的属性

🛠 更新ContentView的属性,位于其开始附近,如下所示。

// [ 📄 ContentView.swift ]

// [ More code here ]

  @State private var userProfile = Profile.empty
  @State private var accessToken: String = ""
  @State private var cachedUserMetadata: [String : Any]? = nil
  
  @State private var isAuthenticated = false
  @State private var isJustLaunched = true
  @State private var personalAffirmation = ""
  // New code 👇🏽👇🏽👇🏽
  @State private var shouldDisplayAnnouncement = false
  @State private var announcement = ""
  @State private var announcementUrl = URL(string: "about:blank")
  // New code 👆🏽👆🏽👆🏽

// [ More code here ]

你刚刚又给ContentView 添加了三个属性。它们都是为 "公告 "链接准备的,而且都是从用户的应用元数据中提取的。

  1. shouldDisplayAnnouncement:一个布尔值,决定 "公告 "链接是否应该被显示。
  2. announcement:链接的文本。
  3. announcementUrl:该链接的URL。

添加 "公告 "链接的屏幕控件

🛠 添加绘制 "公告 "链接的代码,将ContentView'的body 属性更新为以下内容。

// [ 📄 ContentView.swift ]

// [ More code here ]

         VStack { // Affirmation section
            
            // Affirmation label and text field
            Text("Your personal affirmation:")
            TextField("Your affirmation here", text: $personalAffirmation)
              .modifier(TextFieldStyle())
            
            HStack { // Affirmation button row
              Spacer()
              Button("Refresh affirmation") {
                getMetadata()
              }
              Spacer()
              Button("Save affirmation") {
                updateUserMetadata()
              }
              Spacer()
            } // HStack - Affirmation button row
            
          } // VStack - Affirmation section
          
          // New code 👇🏽👇🏽👇🏽
          // Announcement
          if shouldDisplayAnnouncement {
            VStack {
              Spacer()
              Link(
                announcement,
                destination: announcementUrl!)
              .modifier(AnnouncementStyle())
            }
          }
          // New code 👆🏽👆🏽👆🏽
          
          Button("Log out") {
            logout()
          }
          .buttonStyle(BigButtonStyle())
          
        } // VStack
        
      } // ScrollView

    } else {

// [ More code here ]

你刚才输入的代码为应用程序的视图添加了一个 if到应用程序的视图中,包含以下内容。

  • 一个VStack ,只有当shouldDisplayAnnouncement 属性的值是 true.
  • 一个链接,其文本由announcement 属性决定,链接到URL,其值包含在announcementUrl 属性中。

更新 getMetadata()方法

🛠 在ContentView's 的方法中添加一个部分,如下所示: getMetadata()方法,如下图所示。

// [ 📄 ContentView.swift ]

// [ More code here ]

  func getMetadata() {
    if accessToken == "" {
      return
    }
    
    Auth0
      .users(token: accessToken)
      .get(userProfile.id, fields: ["user_metadata", "app_metadata"])
      .start { result in
        
        switch result {
        
          case .failure(let error):
            print("Error: Couldn’t retrieve metadata.\n\(error.localizedDescription)")
          
          case .success(let metadata):
            // Get user metadata
            let userMetadata = metadata["user_metadata"] as? [String: Any]
            self.cachedUserMetadata = userMetadata
            self.personalAffirmation = cachedUserMetadata?["personal_affirmation"] as? String ?? ""
            
            // New code 👇🏽👇🏽👇🏽
            // Get app metadata
            let appMetadata = metadata["app_metadata"] as? [String: Any]
          
            let announcementFlag = appMetadata?["display_announcement"] as? Bool ?? false
            self.announcement = appMetadata?["announcement_text"] as? String ?? ""
            let announcementUrlString = appMetadata?["announcement_url"] as? String ?? ""
            self.announcementUrl = URL(string: announcementUrlString)
          
            self.shouldDisplayAnnouncement = announcementFlag &&
                                             announcement != "" &&
                                             announcementUrlString != ""
            // New code 👆🏽👆🏽👆🏽
            
        } // switch
        
      } // start()
  }

// [ More code here ]

新添加的代码将从Auth0收到的应用程序元数据转换为一个字典,就像前面那行对用户元数据所做的那样。然后它使用该字典的内容来设置ContentView'sshouldDisplayAnnouncement,announcement, 和announcementUrl 属性的值。

试试 "公告 "链接

🛠 运行该应用程序,并以你在本练习中早先创建的用户身份登录。

你会看到该应用程序现在有一个*"点击这里 "的重要公告*链接。

🛠 点击该链接,确认它将带你到一个特别的YouTube公告,该公告是由一个叫Rick Astley的先生发布的。我让读者自己来判断这个消息的重要性。

🛠 切换到Auth0仪表板上的用户页面。滚动到app/metadata 部分,改变`displayannouncementtofalse` 的值。退出,再次登录,然后确认 "公告 "链接不再出现。

结论

恭喜你!你已经完成了这个练习。你已经完成了练习,利用一个基本的基于SwiftUI的iOS应用,使用Auth0认证并添加了对用户元数据的支持。

你做了什么

你使用了两个WebAuth 协议方法: audience()方法来指定你想使用Auth0管理API,和 scope()方法不仅请求登录的默认授权,还请求读取用户的用户元数据应用元数据以及更新用户的用户元数据的授权。

你从用户成功登录时 Auth0 返回的凭证中提取了访问令牌。通过用户的唯一ID,你使用该令牌来获得访问与用户资料相关的元数据的授权。

你使用用户的用户元数据,让他们有能力设置和更新一个用户偏好:他们的个人申明,只要他们登录就会显示。你还使用了用户的应用程序元数据来确定用户是否应该看到一个 "公告 "链接,以及该链接的内容,以及它应该指向哪里。