如何将Auth0规则迁移到Auth0行动中

325 阅读11分钟

比较Auth0规则和行动

Auth0的优势之一是它对可扩展性的关注,即用户能够定制Auth0身份解决方案的标准行为。Auth0规则是第一个使用户能够运行自定义代码来扩展Auth0认证的工具。规则是无服务器的代码块,在认证成功后但在令牌发出前立即执行。

为了满足最广泛的可扩展性需求,Auth0一直在努力改进这个工具。最近,它整合了在定制方面获得的经验,为开发者提供了Auth0 Actions,这是一个坚实的环境,可以满足大多数基于代码的定制需求。

与规则相比,Auth0 Actions提供了很多好处。因此,如果你准备开始你的定制,Actions是推荐的工具。另一方面,如果你已经通过规则实现了你的定制,我们强烈建议你迁移到Actions。让我们来看看转换到Actions的几个优势。

  • 规则让你只处理认证流程。Auth0 Actions让你处理多个流程:认证、用户注册前和注册后、密码更改、客户凭证交换、电话信息发送。
  • 规则没有一个集成的版本控制系统。行动有。你可以修改Action而不影响生产中的Action,创建多个版本,并恢复以前的版本。
  • 与规则不同,Action编辑器支持代码提示、所有npm包和隔离的秘密管理。开发者的体验得到了显著改善

阅读这篇博文,快速了解Auth0 Actions的情况。

虽然Auth0 Actions给你带来了一个改进的定制环境,但Rules和Hooks仍然可用,而且它们都可以一起使用。请记住,规则和钩子是在Actions之前执行的。

你可以使用Actions来代替你的规则,但要注意一些限制。

  • 在规则中,你可以通过向usercontext 对象添加属性,与管道中的其他规则共享数据。这些属性可以在后续的规则中访问。在Actions中,你不能这样做,尽管对用户元数据的改变可以被后续Actions访问。
  • 规则可以修改SAML断言或属性。Actions则不能。
  • 规则对Auth0管理APIglobalauth0 对象访问是有限的。Actions不能,但有另一种方法来完成类似的任务,你会在下面学到。

请看一下当前实现的全部限制清单

考虑到这些注意事项,让我们来看看如何将你的规则转换成Auth0动作。

无代码解决方案

当从规则迁移到动作时,开发者可能还想考虑Auth0市场上的无代码集成(包括动作集成)。这些由合作伙伴建立的集成解决了常见的身份用例,如同意管理、渐进式剖析和用户验证,使开发人员能够更快地进入市场,同时减少与自定义代码相关的开发和维护成本。

代码转换指南

Auth0规则允许你定制认证流程。这意味着你可以将一个规则转换为一个Auth0动作,处理登录流程的登录后触发

这篇文章不会一步一步地指导你创建一个动作或迁移一个规则。它假定你对如何创建一个规则和如何创建一个动作有基本的了解。如果没有,请参考Auth0关于规则创建和动作编写的文档。在这两种情况下,你都需要一个Auth0账户。如果你没有,你可以免费得到一个

Post Login触发器的Actions编辑器看起来像下面这样。

Auth0 Action editor

从概念上讲,要迁移你的规则,你需要把实现它的函数的主体移到下面的函数的主体中。

exports.onExecutePostLogin = async (event, api) => {
};

这不仅仅是一个复制和粘贴的问题。你需要应用一些变化来使转换有效。所以,让我们把重点放在你应该对你的规则的代码进行的具体修改上。

你可以有多个规则来处理你的认证流程。你可以通过保持相同的顺序将这些规则映射到尽可能多的行动中。这将确保你保持相同的功能。

映射参数

Auth0规则是一个有三个参数的JavaScript函数。user,context, 和callback 。实现Action的函数有两个参数 onExecutePostLogin()实现Action的函数有两个参数:eventapi 。除了名字不同,你如何映射这些参数?你的Action如何从参数中获取与你的规则相同的数据?

实际上,Action的设计给你带来了对运行环境传递给Action函数的数据的整合。一般来说,规则中usercontext 对象的所有只读属性都被转移到Actions中的event 对象。任何与运行环境的交互,如登录失败或更新用户元数据,都是通过api 对象的方法实现的。

请参考文档以了解关于 event 对象和 api 对象的更多细节。

访问用户数据

在规则中,你通过访问user 对象参数来获得关于当前用户的数据。在Action中,你需要访问 event.user对象。在这里你可以找到与user 对象相同的数据,除了由外部身份提供者或自定义数据库脚本添加的属性。

给你一个例子,如果你有以下的规则实现。

function myRule(user, context, callback) {
    const userEmail = user.email;
    const userId = user.user_id;
    
    // ... additional code
}

你可以把它转换成一个Action实现,如下所示。

exports.onExecutePostLogin = async (event, api) => {
    const userEmail = event.user.email;
    const userId = event.user.user_id;
    
    // ... additional code
};

请注意,虽然该 user.app_metadata对象在规则中可能是未定义的,而对应的 event.user.app_metadata将总是在Actions中被定义。

访问上下文数据

在规则中,context 对象存储关于当前登录会话的数据。在Action中,你可以在event 对象中找到同样的数据。

考虑一下下面的规则函数。

function myRule(user, context, callback) {
    const clientId = context.clientID;
    const clientMetadata = context.clientMetadata || {};
    
    const connectionId = context.connectionID;
    const connectionMetadata = context.connectionMetadata || {};
    
    const protocol = context.protocol;
    
    const tenant = context.tenant;
    
    // ... additional code
}

在一个Action中,它将变成如下所示。

exports.onExecutePostLogin = async (event, api) => {
    const clientId = event.client.client_id;
    const clientMetadata = event.client.metadata;
    
    const connectionId = event.connection.id;
    const connectionMetadata = event.connection.metadata;
    
    const protocol = event.transaction.protocol;
    
    const tenant = event.tenant.id;
    
    // ... additional code
};

正如你所看到的,context 对象的一些属性已经成为附属于event 对象的几个对象的属性。例如,你有client 对象,connection 对象,等等。换句话说,登录会话数据已经被重构,变得更加清晰有序。

请注意,一些属性名称有不同的拼法。例如,clientID 变成client_idconnectionMetadata 变成 connection.metadata,等等。

请比较Rules中的context 对象Actions中的event 对象的文档,以了解更多差异。

处理依赖关系

Auth0规则允许你包括依赖关系来增强你的代码。你可以利用数以千计的npm包,但不是npm注册表中所有的包都可以利用。如果你需要一个规则中没有的包(甚至只是一个npm包的特定版本),你需要向Auth0请求它。

在Actions中,你可以添加npm注册表中所有可用软件包的依赖关系,这意味着超过一百万个。

另外,与规则不同,在Actions中你不需要在代码中指定包的版本。这意味着,例如,在Rules中,你必须这样声明一个依赖关系。

function myRule(user, context, callback) {
    const axios = require("axios@0.22.0");
    
    // ... additional code
}

在Actions中,你只需在编辑器的模块管理器中指定包的版本,你的代码看起来像下面这样。

const axios = require("axios");

exports.onExecutePostLogin = async (event, api) => {
    // ... additional code
};

注意,你在函数主体之外导入了依赖关系,更像是一个Node.js模块的风格。

转换规则的回调

在规则中,你使用callback 参数来传回你的处理结果。例如,为了把控制权传递给Auth0认证程序,它可能会运行下一个规则,你应该调用 callback()函数,传递当前的usercontext 对象。如果你想停止当前的登录过程,你需要调用 callback()函数,并附上一个错误实例。

对于Actions来说,一切都更直接了当。要传递控制,只需使用 return.要停止登录过程,请调用 api.access.deny()方法。

考虑一下下面的规则函数。

function myRule(user, context, callback) {
    const userAppMetadata = user.app_metadata || {};

    if (userAppMetadata.condition === "success") {
        // This Rule succeeded, proceed with next Rule.
        return callback(null, user, context);
    }
    
    if (userAppMetadata.condition === "failure") {
        // This Rule failed, stop the login with an error response.
        return callback(new Error("Failure message"));
    }
    
    // ... additional code
}

这段代码根据app_metadata 对象的condition 属性的值来决定成功或失败。你可以把这段代码转换为下面的Action函数。

exports.onExecutePostLogin = async (event, api) => {
    if (event.user.app_metadata.condition === "success") {
        // This Action succeeded, proceed with next Action.
        return;
    }
    
    if (event.user.app_metadata.condition === "failure") {
        // This Action failed, stop the login with an error response.
        return api.access.deny("Failure message");
    }
    
    // ... additional code
};

总而言之,你应该在你的规则中把任何 callback()的任何实例,都应该用调用 api.access.deny()的实例,以防失败,或者用 return来代替,如果成功的话。你也可以省略 return语句,如果你的代码运行到了函数主体的末端。

转换实例

现在你有了一些将规则的代码转换为行动的一般准则,让我们看看一些例子。

将自定义索赔添加到一个标记中

规则的一个最常见的用例是能够向一个令牌,无论是ID令牌还是访问令牌,添加自定义索赔。例如,下面的代码片断显示了一个规则向ID令牌添加两个自定义索赔。

function myRule(user, context, callback) {
  const namespace = 'https://myapp.example.com/';
  
  context.idToken[namespace + 'favorite_color'] = user.user_metadata.favorite_color;
  context.idToken[namespace + 'preferred_contact'] = user.user_metadata.preferred_contact;
  callback(null, user, context);
}

这个规则可以转换为一个动作,如下所示。

exports.onExecutePostLogin = async (event, api) => {
  const namespace = 'https://myapp.example.com/';
  
  api.idToken.setCustomClaim(
    `${namespace}/favorite_color`,
    event.user.user_metadata.favorite_color
  );
  
  api.idToken.setCustomClaim(
    `${namespace}/preferred_contact`,
    event.user.user_metadata.preferred_contact
  );
};

在规则中,直接将值分配给 context.idToken对象,而在Action中,你必须使用 api.idToken.setCustomClaim()方法。请注意,在Action中调用的 callback()的调用在Action中消失了。

更新元数据

你可以使用规则来管理用户和应用程序的元数据。下面的例子显示了如何在用户的元数据中存储字体大小的偏好。

function myRule(user, context, callback){
  user.user_metadata = user.user_metadata || {};
  user.user_metadata.preferences = user.user_metadata.preferences || {};
  user.user_metadata.preferences.fontSize = 12;

  // persist the user_metadata update
  auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
    .then(function(){
      callback(null, user, context);
    })
    .catch(function(err){
      callback(err);
    });
}

相应的Action的代码看起来像下面这样。

exports.onExecutePostLogin = async (event, api) => {
  const userPreferences = event.user.user_metadata.preferences || {};
  
  userPreferences.fontSize = 12;
  
  // persist the user_metadata update
  api.user.setUserMetadata("preferences", userPreferences);
};

当规则的代码需要访问返回承诺的 auth0.users.updateUserMetadata()方法,该方法返回一个承诺,而Action的代码只是通过使用 api.user.setUserMetadata()方法来分配元数据值。请注意,与规则的代码相比,Action的代码是多么简化。

在规则中,每个调用的 auth0.users.updateUserMetadata()的每次调用都会向Auth0管理API发出一个HTTP请求。相反,所有的调用 api.user.setUserMetadata()发生在一个或多个Actions中的所有调用将导致在Actions执行结束时的一个API调用。这有助于减轻速率限制错误的风险。

重定向用户

当你需要将用户重定向到一个不同的页面,以完成登录过程,然后恢复认证,你的规则可以如下所示。

function myRule(user, context, callback) {
  if (context.protocol === "redirect-callback") {
    // User was redirected to the /continue endpoint
    user.app_metadata.wasRedirected = true;
    return callback(null, user, context);
  } else if (
    context.protocol === "oauth2-password" ||
    context.protocol === "oauth2-refresh-token" ||
    context.protocol === "oauth2-resource-owner"
  ) {
    // User cannot be redirected
    return callback(null, user, context);
  }

  // User is logging in directly
  if (!user.app_metadata.wasRedirected) {
    context.redirect = {
        url: "https://example.com",
    };
    callback(null, user, context);
  }
}

这个规则和之前的任何其他规则将运行两次:在重定向之前和响应时。通常情况下,你需要把处理重定向和响应的逻辑放在同一个规则中。此外,你还需要手动检查当前协议是否符合用户重定向的条件。

这个规则转换为一个动作,如下所示。

exports.onExecutePostLogin = async (event, api) => {
    if (!event.user.app_metadata.wasRedirected && api.redirect.canRedirect()) {
        api.redirect.sendUserTo("https://example.com");
    }
};

exports.onContinuePostLogin = async (event, api) => {
    api.user.setAppMetadata("wasRedirected", true);
};

你有不同的函数处理重定向 (onExecutePostLogin())和响应(onContinuePostLogin()).另外,检查当前协议是否符合用户重定向的责任被分配给了 api.redirect.canRedirect()方法。另外,请注意,Actions使用 api.redirect.sendUserTo()方法来重定向用户,而不是使用 context.redirect对象。

要了解更多关于用户重定向的操作,请查看文档

计划你的迁移策略

在这一点上,你应该对规则和动作中使用的代码的主要区别有了更清楚的了解。然而,知道如何将一个规则转化为一个动作并不是你应该关心的唯一事情。你还需要计划一个策略,将你的代码从Rules迁移到Action,以避免在生产环境中出现问题。

查看文档,了解定义从规则到动作迁移策略的指南

总结

这篇文章为你提供了建立从Auth0规则到Actions的迁移过程的基础。你学到了迁移的主要原因以及为规则编码和为行动编码的基本区别。

一些转换的例子向你展示了Actions是如何简化代码的。你可以在Auth0规则到行动的迁移指南中找到更多的转换例子和其他信息。