比较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来代替你的规则,但要注意一些限制。
- 在规则中,你可以通过向
user和context对象添加属性,与管道中的其他规则共享数据。这些属性可以在后续的规则中访问。在Actions中,你不能这样做,尽管对用户元数据的改变可以被后续Actions访问。 - 规则可以修改SAML断言或属性。Actions则不能。
- 规则对Auth0管理API和
global和auth0对象的访问是有限的。Actions不能,但有另一种方法来完成类似的任务,你会在下面学到。
请看一下当前实现的全部限制清单。
考虑到这些注意事项,让我们来看看如何将你的规则转换成Auth0动作。
无代码解决方案
当从规则迁移到动作时,开发者可能还想考虑Auth0市场上的无代码集成(包括动作集成)。这些由合作伙伴建立的集成解决了常见的身份用例,如同意管理、渐进式剖析和用户验证,使开发人员能够更快地进入市场,同时减少与自定义代码相关的开发和维护成本。
代码转换指南
Auth0规则允许你定制认证流程。这意味着你可以将一个规则转换为一个Auth0动作,处理登录流程的登录后触发。
这篇文章不会一步一步地指导你创建一个动作或迁移一个规则。它假定你对如何创建一个规则和如何创建一个动作有基本的了解。如果没有,请参考Auth0关于规则创建和动作编写的文档。在这两种情况下,你都需要一个Auth0账户。如果你没有,你可以免费得到一个。
Post Login触发器的Actions编辑器看起来像下面这样。

从概念上讲,要迁移你的规则,你需要把实现它的函数的主体移到下面的函数的主体中。
exports.onExecutePostLogin = async (event, api) => {
};
这不仅仅是一个复制和粘贴的问题。你需要应用一些变化来使转换有效。所以,让我们把重点放在你应该对你的规则的代码进行的具体修改上。
你可以有多个规则来处理你的认证流程。你可以通过保持相同的顺序将这些规则映射到尽可能多的行动中。这将确保你保持相同的功能。
映射参数
Auth0规则是一个有三个参数的JavaScript函数。user,context, 和callback 。实现Action的函数有两个参数 onExecutePostLogin()实现Action的函数有两个参数:event 和api 。除了名字不同,你如何映射这些参数?你的Action如何从参数中获取与你的规则相同的数据?
实际上,Action的设计给你带来了对运行环境传递给Action函数的数据的整合。一般来说,规则中user 和context 对象的所有只读属性都被转移到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_id ,connectionMetadata 变成 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()函数,传递当前的user 和context 对象。如果你想停止当前的登录过程,你需要调用 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规则到行动的迁移指南中找到更多的转换例子和其他信息。