跳转至

使用 SDK 关联和取消关联社交/企业登录提供方

让已登录用户可以在您自己的界面中,将社交或企业登录提供方关联到账号,之后也可以取消关联。本指南以 Google 为示例提供方,但同样的代码适用于您配置的任何 OAuth 提供方。

说明

目前这些 SDK 方法仅在 Web SDK(@authgear/web) 中提供。如果使用其他 SDK,请使用 用户设置 页面,用户可以在那里管理自己的社交/企业登录连接。

您将实现的功能

在您的应用中实现一个页面(例如账号详情页),实现以下功能:

  1. 读取已登录用户已关联的身份。
  2. 当提供方尚未关联时显示 关联 按钮,已关联时显示 取消关联 按钮。
  3. 通过跳转到 Google 的授权同意页完成关联,然后返回您的应用。
  4. 通过跳转到 Authany 托管的确认页面完成取消关联,然后返回您的应用。

这两个操作都通过浏览器跳转完成。用户离开您的应用,在 Google 或 Authany 的页面上完成相应步骤,然后返回到您指定的重定向 URI。您的回调代码需要调用 finish 方法来完成该操作。

前提条件

  • 已在应用中安装并配置 @authgear/web SDK。参见 开始使用。
  • 用户 已登录。关联和取消关联都需要已通过身份验证的会话。如果不存在有效会话,这两个方法都会抛出异常。
  • 已在 Authany 管理台的 社交 / 企业登录 下配置好该 OAuth 提供方,并设置了别名。本指南使用别名 google。
  • 传给每个方法的重定向 URI,需要在管理台中该应用的 重定向 URI 设置下注册。

示例假设您的应用在配置 SDK 时使用了 sessionType: "refresh_token"。

步骤 1:检查提供方是否已关联

调用 fetchUserInfo() 获取用户信息,然后检查 identities 数组。每个 OAuth 身份都有 type === IdentityType.OAuth,以及与您在管理台中配置的别名相匹配的 oauthProviderAlias。

import authgear, { IdentityType } from "@authgear/web";

const PROVIDER_ALIAS = "google";

async function isProviderLinked() {
  const userInfo = await authgear.fetchUserInfo();
  return (userInfo.identities ?? []).some(
    (identity) =>
      identity.type === IdentityType.OAuth &&
      identity.oauthProviderAlias === PROVIDER_ALIAS
  );
}

来自 UserInfo 端点 的原始 identities 声明如下所示:

{
  "https://authgear.com/claims/user/identities": [
    {
      "type": "login_id",
      "login_id_key": "email",
      "login_id_type": "email"
    },
    {
      "type": "oauth",
      "oauth_provider_type": "google",
      "oauth_provider_alias": "google"
    }
  ]
}

根据当前状态渲染对应的按钮。

import { useEffect, useState } from "react";
import authgear, { IdentityType } from "@authgear/web";

const PROVIDER_ALIAS = "google";

function ProviderLinkButton() {
  const [linked, setLinked] = useState<boolean | null>(null);

  useEffect(() => {
    authgear.fetchUserInfo().then((userInfo) => {
      const isLinked = (userInfo.identities ?? []).some(
        (i) =>
          i.type === IdentityType.OAuth &&
          i.oauthProviderAlias === PROVIDER_ALIAS
      );
      setLinked(isLinked);
    });
  }, []);

  if (linked === null) {
    return <p>Loading…</p>;
  }

  return linked ? (
    <button onClick={startUnlink}>Unlink Google</button>
  ) : (
    <button onClick={startLink}>Link Google</button>
  );
}
<div id="provider-container"></div>

<script type="module">
  import authgear, { IdentityType } from "@authgear/web";

  const PROVIDER_ALIAS = "google";
  const container = document.getElementById("provider-container");

  const userInfo = await authgear.fetchUserInfo();
  const linked = (userInfo.identities ?? []).some(
    (i) =>
      i.type === IdentityType.OAuth &&
      i.oauthProviderAlias === PROVIDER_ALIAS
  );

  const button = document.createElement("button");
  if (linked) {
    button.textContent = "Unlink Google";
    button.onclick = startUnlink;
  } else {
    button.textContent = "Link Google";
    button.onclick = startLink;
  }
  container.appendChild(button);
</script>

调用 startLinkOAuth()。SDK 会将浏览器跳转到 Google 的授权同意页,用户在此登录并批准关联。批准后,Google 会跳转回您的 redirectURI。

function startLink() {
  authgear
    .startLinkOAuth({
      oauthProviderAlias: PROVIDER_ALIAS,
      redirectURI: "https://myapp.com/oauth-callback",
      state: "link_oauth",
    })
    .catch((err) => {
      console.error(err);
    });
}
Option Type Required Description
oauthProviderAlias string 是 在管理台中配置的提供方别名,例如 google。
redirectURI string 是 流程结束后浏览器返回的地址。必须在管理台中注册。
state string 否 返回给您回调的 OAuth state 值。您可以用它来区分关联、取消关联和登录回调。

调用 startUnlinkOAuth()。SDK 会将浏览器跳转到 Authany 托管的页面,页面上有取消关联按钮。用户在该页面确认后,Authany 会跳转回您的 redirectURI。

function startUnlink() {
  authgear
    .startUnlinkOAuth({
      oauthProviderAlias: PROVIDER_ALIAS,
      redirectURI: "https://myapp.com/oauth-callback",
      state: "unlink_oauth",
    })
    .catch((err) => {
      console.error(err);
    });
}

选项与 startLinkOAuth() 相同。请使用不同的 state 值,以便您的回调能识别要完成的是哪个操作。

步骤 5:处理回调

当用户返回到您的 redirectURI 时,完成该操作。读取 state 查询参数,以决定调用 finishLinkOAuth() 还是 finishUnlinkOAuth(),然后将用户送回您的账号详情页。

import { useEffect } from "react";
import authgear from "@authgear/web";

function OAuthCallback() {
  useEffect(() => {
    async function finish() {
      // configure() must run before any finish method.
      await authgear.configure({
        clientID: "your-client-id",
        endpoint: "https://myapp.authanyid.com",
        sessionType: "refresh_token",
      });

      const state = new URL(window.location.href).searchParams.get("state");

      try {
        if (state === "link_oauth") {
          await authgear.finishLinkOAuth();
        } else if (state === "unlink_oauth") {
          await authgear.finishUnlinkOAuth();
        }
        // Both actions are done. Return to the account details page.
        window.location.replace("/account");
      } catch (err) {
        console.error(err);
      }
    }
    finish();
  }, []);

  return <p>Finishing…</p>;
}
<script type="module">
  import authgear from "@authgear/web";

  // configure() must run before any finish method.
  await authgear.configure({
    clientID: "your-client-id",
    endpoint: "https://myapp.authanyid.com",
    sessionType: "refresh_token",
  });

  const state = new URL(window.location.href).searchParams.get("state");

  try {
    if (state === "link_oauth") {
      await authgear.finishLinkOAuth();
    } else if (state === "unlink_oauth") {
      await authgear.finishUnlinkOAuth();
    }
    // Both actions are done. Return to the account details page.
    window.location.replace("/account");
  } catch (err) {
    console.error(err);
  }
</script>

注意

如果用户取消操作或流程失败,finishLinkOAuth() 和 finishUnlinkOAuth() 会以 OAuthError 拒绝(reject)。请在跳转前捕获该错误并向用户展示提示信息。