使用Tailwind与JavaScript打造自定义登录页面的秘诀

本文详细介绍了如何使用Tailwind CSS框架、Alpine.js库及Web API对Okta托管的登录页面进行全面定制,包括创建响应式布局、添加自定义页脚和交互式条款模态框,从而实现与品牌风格一致的现代化登录体验。

我们建议将用户重定向到由Okta身份引擎(OIE)提供支持的Okta托管登录页面进行身份验证,这是您自定义构建应用程序最安全的方法。您无需在代码中管理凭证,并且可以利用最强大的身份验证因素,而无需进行任何代码更改。

内置在登录页面中的Okta登录小部件(SIW)承担了支持组织所需身份验证因素的重任。我提到过策略更改不需要任何代码更改吗?

但您可能认为登录页面和SIW有些单调。并且可能对您的需求来说太“Okta”了?如果您能拥有一个这样的页面呢?

一个具有明亮多彩、响应式设计变更的页面,适合现代生活方式。

让我们为登录页面添加一些色彩、活力和自定义元素。

在本教程中,我们将为一个虚构的待办事项应用程序自定义登录页面。我们将进行以下更改:

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

如果您不熟悉该过程,请花点时间阅读这篇关于自定义登录小部件的文章,因为我们将从自定义小部件扩展到增强整个登录页面体验。

在那篇文章中,我们介绍了如何使用设计令牌样式化Gen3 SIW,以及如何使用afterTransform()方法自定义小部件元素。您需要结合这两篇文章的元素来获得最定制的体验。

自定义Okta托管的登录页面

我们首先使用UI中的内置配置选项应用基础配置。添加您最喜欢的主要和次要颜色,然后为您最喜欢的徽标、网站图标和页面背景图像上传。完成后选择“保存”。每个人都有最喜欢的网站图标,对吧?

我将分别使用#ea3eda#ffa738作为主要和次要颜色。

接下来是代码。在“主题”选项卡中:

  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元素添加了样式配置,并配置了登录时标题的文本。按“保存”保存草稿。

我们必须通过将域添加到内容安全策略(CSP)的允许列表中,允许Okta从外部源(谷歌)加载字体资源。

导航到您品牌的登录页面设置的“设置”选项卡。找到“内容安全策略”并按“编辑”。添加外部资源的域。在我们的示例中,我们仅从Google Fonts加载资源,因此我们添加了以下两个域:

1
2
*.googleapis.com
*.gstatic.com

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

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

使用Tailwind CSS构建响应式布局

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

将CDN添加到您的CSP允许列表:

1
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来减轻跨站脚本(XSS)攻击。您可以在CSP快速参考指南上阅读更多关于CSP nonce的信息。

注意 不要将Tailwind从NPM CDN用于生产用例。Tailwind文档指出,由于CDN有限速,这仅用于实验和原型设计。如果您的品牌在其他生产站点使用Tailwind,您很可能已经在Tailwind中定义了自定义混合和主题。因此,请引用您生产的Tailwind资源,以替代我们在本文中使用的CDN。

<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>

注意 我们出于演示和实验目的从NPM CDN包含Alpine。对于生产应用程序,请使用支持生产规模的CDN。NPM CDN应用速率限制以防止生产级使用。

接下来,我们添加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 store 打开模态框
    Alpine.store('modal').show();
    // 清理观察器
    mutationObserver.disconnect();
  }
});

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

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

创建观察器 我们创建观察器并定义观察行为。观察器首先查找具有名为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
14
15
<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;
    }
  });
});

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

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

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

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

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

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

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

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

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

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

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