跳转至

示例:常见使用场景

以下是一些使用 Hook 在身份验证流程中为 Authany 添加操作的示例。这样可以扩展安全功能,例如更精细的访问控制、自适应 MFA 和可变速率限制。

在 Authany 管理台进入高级 > Hook。然后在阻塞事件部分点击新增,选择 “TypeScript”,再点击“编辑脚本”来编写将在所需事件上执行的代码。

如果您想在自己的服务器上运行代码,也可以使用 Webhook。详情请参阅 Webhook。

仅允许公司内网发起注册

您可以使用 user.pre_create 事件,只允许来自特定 IP 的用户注册。

例如,仅当用户位于网络 123.45.67.89 中时才允许注册:

export default async function (
  e: EventUserPreCreate
): Promise<HookResponse> {
  const allowedIp = "123.45.67.89"; // The IP address of the corporate network
  if (e.context.ip_address == allowedIp) {
    return {
      is_allowed: true,
    };
  } else {
    return {
      is_allowed: false,
      reason: "You are not allow to sign up to this organisation",
      title: "Sign-up not allowed!",
    };
  }
}

允许特定地理位置访问

您可以使用 authentication.post_identified 事件,屏蔽或允许来自特定位置的用户。

例如,仅允许来自香港的注册和登录:

export default async function (
  e: EventAuthenticationPostIdentified
): Promise<EventAuthenticationPostIdentifiedHookResponse> {
  if (e.context.geo_location_code === "HK") {
    return {
      is_allowed: true,
    };
  } else {
    return {
      is_allowed: false,
    };
  }
}

根据用户角色控制特定应用的访问权限

您可以使用 authentication.post_identified,根据用户的角色、标准属性或自定义属性,控制其能否在特定应用中完成身份验证。

例如,如果您希望只有角色为 sales 的用户才能访问客户端 ID 为 c8da9b322e1f494e 的应用 crm-system:

export default async function (
  e: EventAuthenticationPostIdentified
): Promise<EventAuthenticationPostIdentifiedHookResponse> {
  if (
    e.context.client_id === "c8da9b322e1f494e" &&
    e.payload.authentication_context?.authentication_flow?.type != null &&
    ["login", "signup"].includes(e.payload.authentication_context?.authentication_flow?.type)
  ) {
    const user = e.payload.authentication_context.user
    if (user?.roles?.includes("sales")) {
      return {
        is_allowed: true,
      };
    } else if (user?.custom_attributes?.can_access_crm === "true") {
      // Alternatively, use custom_attributes to determine if the user is allowed to access the app
      return {
        is_allowed: true,
      };
    } else {
      return {
        is_allowed: false,
        reason: "You don't have permission to use this app"
      };
    }
  }
  // Allow login or signups of other clients
  return {
    is_allowed: true,
  };
}

根据邮箱域名控制访问权限

您可以使用 authentication.post_identified,阻止未使用指定邮箱域名的用户在您的系统中注册。

例如,您只想允许邮箱域名为 @authgear.com 的用户注册:

export default async function (
  e: EventAuthenticationPostIdentified
): Promise<EventAuthenticationPostIdentifiedHookResponse> {
  const email = e.payload.identification.identity?.claims?.email
  if (typeof email === "string" && email.endsWith("@authgear.com")) {
    return {
      is_allowed: true,
    };
  }
  // Block signup of all other emails
  return {
    is_allowed: false,
  };
}

屏蔽周末登录

您可以使用 authentication.post_identified,阻止用户在周末登录。

例如,如果您的业务只在工作日运营,因此不希望任何用户在周末登录:

export default async function (
  e: EventAuthenticationPostIdentified
): Promise<EventAuthenticationPostIdentifiedHookResponse> {
  const today = new Date();
  // 0 is sunday, and 6 is saturday
  if (today.getDay() === 0 || today.getDay() === 6) {
    return {
      is_allowed: false,
    };
  }
  return {
    is_allowed: true,
  };
}

注意:即使周末登录被屏蔽,工作日签发的刷新令牌和访问令牌也不会失效。

根据身份验证方式屏蔽用户登录

您可以使用 authentication.pre_authenticated,根据身份验证过程中使用的 AMR(身份验证方法参考) 屏蔽用户登录。

例如,如果您想屏蔽在身份验证过程中未使用 mfa(多重身份验证)的用户:

export default async function (
  e: EventAuthenticationPreAuthenticated
): Promise<EventAuthenticationPreAuthenticatedHookResponse> {
  if (!e.payload.authentication_context.amr?.includes("mfa")) {
    return {
      is_allowed: false,
    };
  }
  return {
    is_allowed: true,
  };
}

在特定条件下启用机器人防护

使用 authentication.pre_initialize,在特定条件下启用机器人防护。

例如,如果您只想为香港以外的用户显示验证码:

首先,在管理台中启用机器人防护,并将验证要求设置为 “Never”。

然后在您的 authentication.pre_initialize Hook 中返回 bot_protection.mode:

export default async function (
  e: EventAuthenticationPreInitialize
): Promise<EventAuthenticationPreInitializeHookResponse> {
  if (e.context.geo_location_code !== "HK") {
    return {
      is_allowed: true,
      bot_protection: {
        mode: "always",
      },
    };
  }
  // Else, simply allow the login
  return {
    is_allowed: true,
  };
}

这会覆盖配置中 bot_protection 原有的 mode 值。因此,当用户尝试在香港以外的地区注册或登录时,bot_protection 将会启用。

自适应 MFA:仅对高风险用户要求 MFA

您可以使用 authentication.pre_authenticated 来实现自适应 MFA。

例如,您认为来自 HK 以外的登录风险更高,因此应当要求 MFA:

export default async function (
  e: EventAuthenticationPreAuthenticated
): Promise<EventAuthenticationPreAuthenticatedHookResponse> {
  if (e.context.geo_location_code !== "HK") {
    return {
      // Allow the login with a mfa contraint
      is_allowed: true,
      constraints: {
        amr: ["mfa"],
      },
    };
  }
  // Else, simply allow the login
  return {
    is_allowed: true,
  };
}

如果响应中返回的 constraints.amr 值为 ["mfa"],则根据身份验证流程类型的不同:

  • 注册 / 升级:如果用户在流程中没有设置任何次要身份验证器,系统会在流程末尾添加一个步骤,强制用户设置次要身份验证器。
  • 登录 / 重新验证:如果用户在流程中没有使用任何次要身份验证器,系统会在流程末尾添加一个双重验证步骤。如果用户此前从未设置过双重验证,该流程会失败。
  • 账号找回:无影响,因为账号找回不支持双重验证。

了解更多关于 AMR 的信息,请参阅 AMR(身份验证方法参考)。

进阶:为特定 IP 范围的身份验证应用更严格的速率限制

您可以使用 Hook,为特定 IP 范围的身份验证流程应用更严格的速率限制。

例如,您希望将账号枚举限制为来自数据中心 IP 地址的请求每分钟 5 次,其他请求每分钟 10 次。

步骤 1:调整基础速率限制

首先,在项目配置中添加以下速率限制配置。(管理台 > 高级 > 编辑配置)

authentication:
  rate_limits:
    account_enumeration:
      per_ip:
        enabled: true
        period: 1m
        burst: 10

这会将账号枚举的基础速率限制设置为每分钟 10 次。

步骤 2:在 Hook 中通过权重覆盖速率限制

然后,在 authentication.pre_initialize 事件中创建以下 Hook:

function ipToBinary(ip: string): string {
  return ip
    .split(".")
    .map((octet) => parseInt(octet, 10).toString(2).padStart(8, "0"))
    .join("");
}

function cidrToPrefix(cidr: string): string {
  const [ip, lengthStr] = cidr.split("/");
  const prefixLength = parseInt(lengthStr, 10);
  const ipBinary = ipToBinary(ip);
  return ipBinary.substring(0, prefixLength);
}

export default async function (
  e: EventAuthenticationPreInitialize
): Promise<EventAuthenticationPreInitializeHookResponse> {
  if (e.context.ip_address == null) {
    // ip unknown, block it.
    return {
      is_allowed: false,
    };
  }

  const ipBinary = ipToBinary(e.context.ip_address);

  // ip ranges from https://www.gstatic.com/ipranges/cloud.json
  const dataCenterIPRanges = ["34.125.0.0/16", "34.124.24.0/21"];
  // 34.125.0.0/16
  if (dataCenterIPRanges.some((cidr) => ipBinary.startsWith(cidrToPrefix(cidr)))) {
    return {
      is_allowed: true,
      rate_limits: {
        "authentication.account_enumeration": {
          weight: 2,
        },
      },
    };
  } else {
    return {
      is_allowed: true,
    };
  }
}

将 "rate_limits.authentication.account_enumeration.weight" 设置为 2 后,每次账号枚举尝试都会占用速率限制中的 2 次配额。因此,每分钟只允许 5 次尝试。(10 / 2 = 5)。了解更多请参阅覆盖速率限制