使用Tailwind CSS与JavaScript定制Okta登录页面的技术实现

本文详细介绍了如何通过定制Okta托管登录页面来增强用户体验。文章使用Tailwind CSS框架创建响应式布局,利用Alpine.js库添加交互式条款模态框,并演示了如何通过Web API控制页面行为,提供了完整的前端技术栈实践方案。

使用Tailwind CSS与JavaScript解锁自定义登录页面的奥秘

本文指导您如何为虚构的待办事项应用程序定制Okta托管登录页面。我们将进行以下更改:

  1. 使用Tailwind CSS框架创建响应式登录页面布局
  2. 添加用于自定义品牌链接的页脚
  3. 使用Alpine.js显示用户必须在认证前接受的条款和条件模态框

准备工作

要跟随本教程,您需要:

  • 一个具有身份引擎的Okta账户,例如免费集成账户。
  • 您自己的域名。
  • 对HTML、CSS和JavaScript有基本的了解。
  • 构思好的品牌设计。随时发挥您的创造力!
  • 通过遵循之前的博客文章了解如何自定义登录页面。

自定义您的Okta托管登录页面

我们首先使用UI中的内置配置选项应用基本配置。添加您喜欢的主色和辅色,然后上传您喜欢的徽标、网站图标和页面背景图像。完成后选择“保存”。

在“主题”选项卡中:

  1. 在下拉菜单中选择“登录页面”。
  2. 选择“自定义”按钮。
  3. 在“页面设计”选项卡上,选择代码编辑器切换开关以查看HTML页面。

您将看到轻量级IDE已经搭建了代码。按“编辑”并用以下代码替换现有代码。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta name="robots" content="noindex,nofollow" />
  <!-- 从主题生成的样式 -->
  <link href="{{themedStylesUrl}}" rel="stylesheet" type="text/css">
  <!-- 来自主题的网站图标 -->
  <link rel="shortcut icon" href="{{faviconUrl}}" type="image/x-icon">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link
      href="https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&family=Manrope:wght@200..800&display=swap"
      rel="stylesheet">
  <title>{{pageTitle}}</title>
  {{{SignInWidgetResources}}}

  <style nonce="{{nonceValue}}">
    :root {
      --font-header: 'Inter Tight', sans-serif;
      --font-body: 'Manrope', sans-serif;
      --color-gray: #4f4f4f;
      --color-fuchsia: #ea3eda;
      --color-orange: #ffa738;
      --color-azul: #016fb9;
      --color-cherry: #ea3e84;
      --color-purple: #b13fff;
      --color-black: #191919;
      --color-white: #fefefe;
      --color-bright-white: #fff;
      --border-radius: 4px;
      --color-gradient: linear-gradient(12deg, var(--color-fuchsia) 0%, var(--color-orange) 100%);
    }

    {{#useSiwGen3}}
      html {
        font-size: 87.5%;
      }
    {{/useSiwGen3}}

    #okta-auth-container {
      display: flex;
      background-image: {{bgImageUrl}};
    }

    #okta-login-container {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      width: 50vw;
      background: var(--color-white);
    }
  </style>
</head>

<body>
  <div id="okta-auth-container">
    <div id="okta-login-container"></div>
  </div>

  <!--
   "OktaUtil" 定义了一个全局 OktaUtil 对象
   包含用于完成 Okta 登录流程的方法。
  -->
  {{{OktaUtil}}}

  <script type="text/javascript" nonce="{{nonceValue}}">
    // "config" 对象包含默认的小部件配置
    // 以及您在管理设置中定义的任何自定义覆盖。

    const config = OktaUtil.getSignInWidgetConfig();
    config.theme = {
      tokens: {
        BorderColorDisplay: 'var(--color-bright-white)',
        PalettePrimaryMain: 'var(--color-fuchsia)',
        PalettePrimaryDark: 'var(--color-purple)',
        PalettePrimaryDarker: 'var(--color-purple)',
        BorderRadiusTight: 'var(--border-radius)',
        BorderRadiusMain: 'var(--border-radius)',
        PalettePrimaryDark: 'var(--color-orange)',
        FocusOutlineColorPrimary: 'var(--color-azul)',
        TypographyFamilyBody: 'var(--font-body)',
        TypographyFamilyHeading: 'var(--font-header)',
        TypographyFamilyButton: 'var(--font-header)',
        BorderColorDangerControl: 'var(--color-cherry)'
      }
    }

    config.i18n = {
      'en': {
        'primaryauth.title': '登录以创建任务',
      }
    }

    // 渲染 Okta 登录小部件
    const oktaSignIn = new OktaSignIn(config);
    oktaSignIn.renderEl({ el: '#okta-login-container' },
      OktaUtil.completeLogin,
      function (error) {
        // 记录配置小部件时发生的错误。
        // 删除或替换为您自己的自定义错误处理程序。
        console.log(error.message, error);
      }
    );
  </script>
</body>
</html>

此代码为SIW元素添加了样式配置,并配置了登录时的标题文本。按“保存”到草稿。

我们必须允许Okta从外部来源Google加载字体资源,方法是将其域名添加到内容安全策略的允许列表中。导航到您的品牌登录页面的“设置”选项卡。找到“内容安全策略”并按“编辑”。添加外部资源的域名。在我们的示例中,我们只从Google字体加载资源,因此我们添加了以下两个域名:

  • *.googleapis.com
  • *.gstatic.com

选择“保存到草稿”,然后“发布”以查看您的更改。

登录页面看起来比之前更具风格。如果您尝试调整浏览器窗口大小,我们会发现它不能很好地处理不同的表单因素。让我们使用Tailwind CSS来添加响应式布局。

使用Tailwind CSS构建响应式布局

Tailwind使交付外观酷炫的网站比手动编写CSS快得多。出于演示目的,我们将通过CDN加载Tailwind。

将CDN添加到您的CSP允许列表: https://cdn.jsdelivr.net

导航到“页面设计”,然后“编辑”页面。在<head>中添加加载Tailwind资源的脚本。我将其添加在<style></style>定义之后,</head>之前。

1
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4" nonce="{{nonceValue}}"></script>

加载外部资源(如样式和脚本)需要CSP nonce来缓解跨站脚本攻击。您可以在CSP快速参考指南上阅读更多关于CSP nonce的信息。

<style></style>部分删除#okta-auth-container#okta-login-container的样式。我们可以使用Tailwind来处理它。<style></style>部分应仅包含:root中定义的CSS自定义属性和使用SIW Gen3的指令。

添加Tailwind的样式。我们将添加类,以便在较小的表单因素中显示不带英雄图像的登录容器,然后根据断点显示具有不同宽度的英雄图像。

两个div容器如下所示:

1
2
3
<div id="okta-auth-container" class="h-screen flex bg-(--color-gray) bg-[{{bgImageUrl}}]">
  <div id="okta-login-container" class="w-full min-w-sm lg:w-2/3 xl:w-1/2 bg-(image:--color-gradient) lg:bg-none bg-(--color-white) flex justify-center items-center"></div>
</div>

保存文件并发布更改。随时测试一下!

在Okta托管登录页面上使用Tailwind处理自定义HTML元素

Tailwind擅长为网站添加样式化的HTML元素。我们也可以利用这一点。假设您希望通过添加指向您品牌网站的链接页脚来保持从您的网站到登录页面的网页连续性。添加这个新部分涉及更改HTML节点结构和样式化元素。

我们想要一个固定在视图底部的页脚,因此我们需要一个新的具有垂直堆叠的父容器,并确保页脚的高度保持一致。将HTML节点结构替换为如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<div class="flex flex-col min-h-screen">
  <div id="okta-auth-container" class="flex grow bg-(--color-gray) bg-[{{bgImageUrl}}]">
    <div class="w-full min-w-sm lg:w-2/3 xl:w-1/2 bg-(image:--color-gradient) lg:bg-none bg-(--color-white) flex justify-center items-center">
        <div id="okta-login-container"></div>
    </div>
  </div>
  <footer class="font-(family-name:--font-body)">
    <ul class="h-12 flex justify-evenly items-center text-(--color-azul)">
      <li><a class="hover:text-(--color-orange) hover:underline" href="https://developer.okta.com">条款</a></li>
      <li><a class="hover:text-(--color-orange) hover:underline" href="https://developer.okta.com">文档</a></li>
      <li><a class="hover:text-(--color-orange) hover:underline" href="https://developer.okta.com/blog">博客</a></li>
      <li><a class="hover:text-(--color-orange) hover:underline" href="https://devforum.okta.com">社区</a></li>
    </ul>
  </footer>
</div>

所有内容都重定向到Okta开发者网站。😊 我还保持了字体、文本颜色和文本装饰样式以匹配SIW元素。CSS自定义属性使一致性易于管理。

随时保存并发布以查看效果!

使用外部库在Okta托管登录页面上添加自定义交互性

Tailwind在样式化HTML元素方面很出色,但它不是JavaScript库。如果我们想在登录页面上添加交互元素,我们必须依赖Web API或库来帮助我们。假设我们想确保登录待办事项应用程序的用户同意条款和条件。我们需要一个模态框,在用户同意之前阻止与SIW的交互。

我们将使用Alpine来完成繁重的工作,因为它是一个轻量级的JavaScript库,适合这个需求。我们通过NPM CDN添加该库,因为我们已经在CSP中允许了该域名。将以下内容添加到HTML的<head></head>部分。我将我的直接添加在Tailwind脚本之后。

1
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" nonce="{{nonceValue}}"></script>

接下来,我们添加HTML标签以支持模态框。将HTML节点结构替换为如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<div class="flex flex-col min-h-screen">
  <div id="modal"
    x-data
    x-cloak
    x-show="$store.modal.open"
    x-transition:enter="transition ease-out duration-300"
    x-transition:enter-start="opacity-0"
    x-transition:enter-end="opacity-100"
    x-transition:leave="transition ease-in duration-200"
    x-transition:leave-start="opacity-100"
    x-transition:leave-end="opacity-0 hidden"
    class="fixed inset-0 z-50 flex items-center justify-center bg-(--color-black)/80 bg-opacity-50">
    <div x-transition:enter="transition ease-out duration-300"
         x-transition:enter-start="opacity-0 scale-90"
         x-transition:enter-end="opacity-100 scale-100"
         x-transition:leave="transition ease-in duration-200"
         x-transition:leave-start="opacity-100 scale-100"
         x-transition:leave-end="opacity-0 scale-90"
         class="bg-(--color-white) rounded-(--border-radius) shadow-lg p-8 max-w-md w-full mx-4">
      <h2 class="text-2xl font-(family-name:--font-header) text-(--color-black) mb-4 text-center">欢迎使用待办事项应用</h2>
      <p class="text-(--color-black) mb-6">此应用处于测试阶段。感谢您同意我们的条款和条件。</p>
      <button @click="$store.modal.hide()"
              class="w-full bg-(--color-fuchsia) hover:bg-(--color-orange) text-(--color-bright-white) font-medium py-2 px-4 rounded-(--border-radius) transition duration-200">
          同意
      </button>
    </div>
  </div>
  <div id="okta-auth-container" class="flex grow bg-(--color-gray) bg-[{{bgImageUrl}}]">
    <div class="w-full min-w-sm lg:w-2/3 xl:w-1/2 bg-(image:--color-gradient) lg:bg-none bg-(--color-white) flex justify-center items-center">
      <div id="okta-login-container"></div>
    </div>
  </div>
  <footer class="font-(family-name:--font-body)">
    <ul class="h-12 flex justify-evenly items-center text-(--color-azul)">
      <li><a class="hover:text-(--color-orange) hover:underline" href="https://developer.okta.com">条款</a></li>
      <li><a class="hover:text-(--color-orange) hover:underline" href="https://developer.okta.com">文档</a></li>
      <li><a class="hover:text-(--color-orange) hover:underline" href="https://developer.okta.com/blog">博客</a></li>
      <li><a class="hover:text-(--color-orange) hover:underline" href="https://devforum.okta.com">社区</a></li>
    </ul>
  </footer>
</div>

添加的内容很多,但我想要平滑的过渡动画。😅 内置的进入和离开状态使得添加过渡动画比手动操作容易得多。

注意我们使用状态值来确定是否显示模态框。我们使用全局状态管理,下一步是设置它。在<script></script>部分中找到注释// 渲染 Okta 登录小部件,并在Alpine初始化后添加以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
document.addEventListener('alpine:init', () => {
  Alpine.store('modal', {
    open: true,
    show() {
      this.open = true;
    },
    hide() {
      this.open = false;
    }
  });
});

事件监听器监视alpine:init事件并运行一个函数,该函数在Alpine的存储中定义一个元素modalmodal存储包含一个跟踪其是否打开的属性以及一些用于显示和隐藏的辅助方法。

当您保存并发布时,您将在网站重新加载时看到模态框!

我们使模态框保持固定,即使用户按Esc键或选择遮罩。用户必须同意条款才能继续。

使用Web API自定义Okta托管登录页面行为

我们在网页加载后立即显示模态框。它有效,但我们也可以在登录小部件渲染后显示模态框。这样做允许我们使用Alpine支持的漂亮的进入和离开CSS过渡。我们希望监视<div id="okta-login-container"></div>内的DOM更改。这是渲染SIW的父容器。我们可以使用MutationObserver Web API并监视div内的DOM突变。

<script></script>部分中,在alpine:init的事件监听器之后,添加以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const loginContainer = document.querySelector("#okta-login-container");

// 使用 MutationObserver 监视身份验证容器元素
const mutationObserver = new MutationObserver(() => {
  const element = loginContainer.querySelector('[data-se*="auth-container"]');
  if (element) {
    document.getElementById('modal').classList.remove('hidden');
    // 使用 Alpine 存储打开模态框
    Alpine.store('modal').show();
    // 清理观察器
    mutationObserver.disconnect();
  }
});

mutationObserver.observe(loginContainer, {
  childList: true,
  subtree: true
});

让我们逐步了解代码的作用。首先,我们创建一个变量来引用SIW的父容器,因为我们将用它作为我们工作的根元素。突变观察器可能会对性能产生负面影响,因此尽可能限制观察器的范围至关重要。

创建观察器 我们创建观察器并定义观察行为。观察器首先查找具有名为se的数据属性的元素,该属性包含值auth-container。Okta会为内部操作添加一个带有数据属性的节点。我们也将为我们的内部操作做同样的事情。😎

定义观察时的行为 一旦我们找到与auth-container数据属性匹配的元素,我们就显示模态框,这会触发进入过渡动画。然后我们清理观察器。

确定要观察的内容 我们开始观察DOM,并传入用作根的元素,以及指定要监视内容的配置。我们希望观察子元素和从根开始的子树中的更改,以找到SIW元素。

最后,让我们启用模态框以根据观察器触发。我故意提供了强制模态框在SIW渲染之前显示的代码片段,以便您在我们进行过程中可以预览您的工作。

在HTML节点结构中,找到<div id="modal">。它缺少一个最初隐藏模态框的类。将类hidden添加到类列表中。<div>的类列表应如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<div id="modal"
    x-data
    x-cloak
    x-show="$store.modal.open"
    x-transition:enter="transition ease-out duration-300"
    x-transition:enter-start="opacity-0"
    x-transition:enter-end="opacity-100"
    x-transition:leave="transition ease-in duration-200"
    x-transition:leave-start="opacity-100"
    x-transition:leave-end="opacity-0 hidden"
    class="hidden fixed inset-0 z-50 flex items-center justify-center bg-(--color-black)/80 bg-opacity-50">
<!-- 剩余的模态结构在此处。将您的工作与上面的类列表进行比较 -->
</div>

然后,在alpine:init事件监听器中,将模态框的open属性默认更改为false

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
document.addEventListener('alpine:init', () => {
  Alpine.store('modal', {
    open: false,
    show() {
      this.open = true;
    },
    hide() {
      this.open = false;
    }
  });
});

保存并发布您的更改。您现在会注意到模态框淡入视图前有轻微的延迟。非常流畅!

值得注意的是,我们的解决方案并非万无一失;精明的用户可以通过操作浏览器调试器中的元素来隐藏模态框并继续与登录小部件交互。您需要添加额外的检查和更健壮的代码来实现可靠的方法。尽管如此,这个示例提供了功能的一般概念以及如何为登录体验添加交互组件的方法。

不要忘记测试对登录页面进行的任何实现更改的可访问性。默认站点和登录小部件是可访问的。我们做出的任何更改或自定义都可能改变站点的可访问性。

您将使用与您的品牌自定义URL匹配的颁发者值以及Okta应用程序的客户端ID,用于您的自定义应用程序的OIDC配置中。

添加Tailwind、Web API和JavaScript库来自定义您的Okta托管登录页面

我希望您觉得这篇文章有趣,并解锁了您可以自定义Okta托管登录小部件体验的潜力。

您可以在GitHub仓库中找到此项目的最终代码。

如果您喜欢这篇文章,请查看这些资源。

  • 发挥想象力,打造愉悦的登录体验
  • Okta登录小部件

请记住在LinkedIn上关注我们,并在YouTube上订阅我们以获取更多精彩内容。让我们知道您如何自定义Okta托管的登录页面。我们很乐意看到您的成果。

我们也想听听您希望看到哪些主题以及您可能有哪些问题。请在下方给我们留言!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计