跳转至

Ionic SDK

在本文中,您将学习如何在 Ionic 项目中通过 Authany Ionic SDK 使用 Authany。

说明

本教程的示例应用完整代码可以在这个 Github 仓库中找到。

根据 Capacitor v7 的要求,以下是您的原生应用可以支持的 Android 和 iOS 最低版本:

  • iOS 14
  • Android 6(API 级别 23)

Objectives (What we'll build)

完成本教程后,我们将构建一个具备以下功能的 Ionic 应用:

  • 允许用户登录其 Authany 项目下的账号
  • 允许新用户注册
  • 允许已登录用户查看用户信息并登出

我们将要构建的应用最终界面如下所示:

Authany Ionic 示例应用首页

Prerequisites

为顺利完成本指南,请确保已具备以下条件:

  • 本地机器上已安装 Node.js
  • Android Studio(用于构建应用的 Android 客户端)
  • Xcode(用于构建应用的 iOS 客户端)
  • 一个 Authany 账号。您可以点击这里免费注册。
  • 任意代码编辑器(VS Code、Sublime 等)

按照本指南,只需 🕐 10 分钟即可将 Authany 添加到您的 Ionic 应用中。

Setup Application in Authany Portal

在这部分,您将学习如何配置将在 Ionic 项目中使用的 Authany 客户端应用。您需要在 Authany 管理台中完成以下步骤。

Step 1: Set up an Authany Application

首先,登录 https://manage.authany.com/ 上的 Authany 管理台,选择一个已有项目或创建一个新项目。

在您的项目中,进入 应用 部分,然后点击 Add Application 创建一个新的 Authany 应用。为应用输入一个名称,并将 Application Type 选择为 Native App。接着点击 Save,进入新应用的配置页面。可以跳过展示不同框架教程列表的页面。

创建新的 Authany 客户端应用

Step 2: Add Authorized Redirect URIs

在这一步,您将为应用设置授权重定向 URI。授权重定向 URI 应指向 Ionic 应用中的一个页面,即授权流程结束时希望将用户重定向到的位置。

要添加 URI,请滚动到应用配置页面的 URIs 部分,在文本框中输入 URI。您可以点击 Add URI 按钮添加更多 URI。

在我们的示例应用中,添加以下 URI:

  • com.authgear.example.capacitor://host/path
  • capacitor://localhost
  • http://localhost:8100/oauth-redirect
  • https://localhost

Authany 应用重定向 URI

完成后,点击 Save 按钮。

Add Authany to an Ionic App

现在您已经配置好 Authany 应用,可以开始创建具备前面目标中所述全部功能的 Ionic 应用了。

在本教程中,我们将使用 React 实现一个 Ionic 应用。

Step 1: Create Ionic Project

在创建 Ionic 项目之前,请在终端或命令提示符中运行以下命令,在计算机上安装 Ionic CLI:

npm install -g @ionic/cli native-run cordova-res

现在运行以下命令创建一个新的 Ionic 项目:

ionic start authgear-ionic-example --type=react --capacitor

运行上述命令后,按照向导创建一个新的空白项目(blank project)。

接着,在代码编辑器中打开新项目,将 capacitor.config.ts 中的 appId 更新为以下值:

appId: 'com.authgear.example.capacitor',

这个新的 appId 值与我们之前在授权重定向 URI 中使用的值相同。

说明

注意: 请务必在为 Ionic 应用创建 Android 和 iOS 项目之前更新 appId 的值。这样 Capacitor 才能使用该 appId 值作为包名和 App ID 来创建您的 Android 和 iOS 项目。

在新 Ionic 项目的根目录下运行以下命令,即可在浏览器中预览空白项目:

ionic serve

最后,在 Ionic 项目的根目录下运行以下命令,为应用创建 Android 和 iOS 项目:

首先,安装 Android 和 iOS 平台:

npm i @capacitor/android @capacitor/ios

然后,创建项目:

npx cap add android
npx cap add ios

Step 2: Install Authany SDK

在这一步,您将安装用于 Ionic(Capacitor)的 Authany SDK,以及用于 Web 的 JavaScript SDK。Web SDK 可以帮助您在浏览器中测试应用。

要安装这些 SDK,请在终端或命令提示符中运行以下命令:

Authany Ionic SDK

npm i @authgear/capacitor

Authany Web SDK

npm i @authgear/web

Step 3: Configure Authany SDK

在这一步,您将学习如何使用 Authany 应用配置中的信息来配置 Ionic 项目。

首先,在代码编辑器中打开 src/pages/Home.tsx,在文件顶部添加以下代码以导入 Authany SDK:

import authgearWeb, {
  SessionState,
  WebContainer,
  SessionStateChangeReason,
} from "@authgear/web";
import authgearCapacitor, {
  CapacitorContainer,
  Page,
} from "@authgear/capacitor";

上面的代码导入了示例应用所需的全部 Authany SDK 组件。

由于 Ionic 应用既可以在 Web 上运行,也可以在原生移动平台上运行,因此我们需要同时导入 Authany Web SDK 和 Authany Capacitor SDK。

接着,在 Home.tsx 最后一条 import 语句下方添加以下常量:

const CLIENT_ID = "<ClIENT_ID>";
const ENDPOINT = "<AUTHGEAR_ENDPOINT>";
const REDIRECT_URI_WEB_AUTHENTICATE = "http://localhost:8100/oauth-redirect";
const REDIRECT_URI_CAPACITOR = "com.authgear.example.capacitor://host/path";
const REDIRECT_URI_WEB_REAUTH = "http://localhost:8100/reauth-redirect";

将常量(CLIENT_ID、ENDPOINT)的值更新为 Authany 管理台中客户端应用配置页面上的正确值。

现在,在常量下方添加这个小工具函数,用于判断 Ionic 应用是运行在原生环境还是浏览器中:

function isPlatformWeb(): boolean {
  return Capacitor.getPlatform() === "web";
}

在 Home.tsx 顶部的 import 部分添加以下内容以导入 Capacitor:

import { Capacitor } from "@capacitor/core";

您将使用上面的函数,根据用户当前所在的平台来决定调用哪个 Authany SDK 实例。

现在,将以下代码粘贴到 Home.tsx 中,实现一个新的 AuthenticationScreen 组件:

function AuthenticationScreen() {

  const [sessionState, setSessionState] = useState<SessionState | null>(() => {
    if (isPlatformWeb()) {
      return authgearWeb.sessionState;
    }
    return authgearCapacitor.sessionState;
  });

  const loggedIn = sessionState === "AUTHENTICATED";

  const delegate = useMemo(() => {
    const d = {
      onSessionStateChange: (
        container: WebContainer | CapacitorContainer,
        _reason: SessionStateChangeReason
      ) => {
        setSessionState(container.sessionState);
      },
    };
    return d;
  }, [setSessionState]);

    useEffect(() => {
    if (isPlatformWeb()) {
      authgearWeb.delegate = delegate;
    } else {
      authgearCapacitor.delegate = delegate;
    }

    return () => {
      if (isPlatformWeb()) {
        authgearWeb.delegate = undefined;
      } else {
        authgearCapacitor.delegate = undefined;
      }
    };
  }, [delegate]);

  return (
    <>
      <div className="container">

      </div>
    </>
  );
}

在 Home.tsx 顶部导入 useState、useMemo、useEffect 和 useCallback:

import React, { useState, useEffect, useCallback, useMemo } from "react";

上面的代码还实现了一个 sessionState 常量,以及一个用于在用户会话状态发生变化时通知应用的委托。

接着,在 AuthenticationScreen 组件的 return 语句之前,添加以下 configure() 方法:

const configure = useCallback(async () => {
  try {
    if (isPlatformWeb()) {
      await authgearWeb.configure({
        clientID: CLIENT_ID,
        endpoint: ENDPOINT,
        sessionType: "refresh_token",
        isSSOEnabled: false,
      });
    } else {
      await authgearCapacitor.configure({
        clientID: CLIENT_ID,
        endpoint: ENDPOINT,
      });
    }
    await postConfigure();
  } catch (e) {
    console.error("Authgear Configuration error:", e);
  } 
}, [CLIENT_ID, ENDPOINT]);

上面的代码使用我们在本指南第一部分创建的 Authany 客户端应用的 Client ID 和 Endpoint,配置了一个新的 Authany SDK 实例。

接着,实现在 configure() 方法中调用的 postConfigure() 方法:

const postConfigure = useCallback(async () => {
  const sessionState = isPlatformWeb()
    ? authgearWeb.sessionState
    : authgearCapacitor.sessionState;

  // if user has an existing session, call SDK fetchUserInfo method to get the user's info and refresh access token when necessary
  if (sessionState === "AUTHENTICATED") {
    if (isPlatformWeb()) {
      await authgearWeb.fetchUserInfo();
    } else {
      await authgearCapacitor.fetchUserInfo();
    }
  }
}, []);

postConfigure() 方法会检查用户是否已通过身份验证,并调用 Authany SDK 的 fetchUserInfo() 方法。调用 fetchUserInfo() 方法会在用户的访问令牌过期时将其刷新。

现在,在 postConfigure() 方法之后添加以下代码,在 useEffect 中调用 configure() 方法:

useEffect(() => {
  configure();
}, []);

上面的 useEffect 会在页面加载时初始化 Authany。

Step 4: Add Login Button

在这一步,我们将添加登录按钮,以及要展示给已通过身份验证用户的 UI 组件。

在 AuthenticationScreen 组件 return 语句中的 <div> 内添加以下代码:

<h1>
  Welcome
</h1>
{!loggedIn ? (
    <IonButton
    className="button"
    // disabled={!initialized || loading || loggedIn}
    onClick={
        (event) => {
            onClickAuthenticate(event, "login")
        }
    }
    >
    Login
</IonButton>
):
(
    <div>
        <p>Welcome user</p>
    </div>
)}

将 IonButton 添加到应用中其他 Ionic 组件的 import 语句中:

import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonButton } from '@ionic/react';

最后,将 <AuthenticationScreen/> 组件添加到 Home 组件中:

const Home: React.FC = () => {
  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Blank</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent fullscreen>
        <IonHeader collapse="condense">
          <IonToolbar>
            <IonTitle size="large">Blank</IonTitle>
          </IonToolbar>
        </IonHeader>
        <AuthenticationScreen />
      </IonContent>
    </IonPage>
  );
};

Step 5: Start Authentication

在这一步,我们将在上一步创建的 AuthenticationScreen 组件内实现一个 authenticate() 方法。

为此,首先在 AuthenticationScreen() 组件的 return 语句之前添加以下代码:

const authenticate = useCallback(async (page: string) => {
  try {
    if (isPlatformWeb()) {
      authgearWeb.startAuthentication({
        redirectURI: REDIRECT_URI_WEB_AUTHENTICATE,
        page: page,
      });
    } else {
      const result = await authgearCapacitor.authenticate({
        redirectURI: REDIRECT_URI_CAPACITOR,
        page: page,
      });
    }
  } catch (e) {
    console.error("Authentication error:", e);
  } 
}, []);

调用这个 authenticate() 方法会启动身份验证流程。page 参数可用于指定在 login 页面还是 signup 页面启动身份验证流程。

最后,实现一个 onClickAuthenticate() 方法,在按下登录按钮时调用 authenticate:

const onClickAuthenticate = useCallback(
  (e: MouseEvent<HTMLIonButtonElement>, page: string) => {
    e.preventDefault();
    e.stopPropagation();

    authenticate(page);
  },
  [authenticate]
);

导入 MouseEvent:

import type { MouseEvent } from "react";

Checkpoint

至此,Home.tsx 的完整代码应如下所示:

import {
  IonContent,
  IonHeader,
  IonPage,
  IonTitle,
  IonToolbar,
  IonButton,
} from "@ionic/react";

import React, { useState, useEffect, useCallback, useMemo } from "react";
import { Capacitor } from "@capacitor/core";
import type { MouseEvent } from "react";

import authgearWeb, {
  SessionState,
  WebContainer,
  SessionStateChangeReason,
} from "@authgear/web";
import authgearCapacitor, {
  CapacitorContainer,
  Page,
} from "@authgear/capacitor";
import "./Home.css";

const CLIENT_ID = <ClIENT_ID>";
const ENDPOINT = "<AUTHGEAR_ENDPOINT>";
const REDIRECT_URI_WEB_AUTHENTICATE = "http://localhost:8100/oauth-redirect";
const REDIRECT_URI_CAPACITOR = "com.authgear.example.capacitor://host/path";

function isPlatformWeb(): boolean {
  return Capacitor.getPlatform() === "web";
}

function AuthenticationScreen() {
  const [sessionState, setSessionState] = useState<SessionState | null>(() => {
    if (isPlatformWeb()) {
      return authgearWeb.sessionState;
    }
    return authgearCapacitor.sessionState;
  });

  const loggedIn = sessionState === "AUTHENTICATED";
  const delegate = useMemo(() => {
    const d = {
      onSessionStateChange: (
        container: WebContainer | CapacitorContainer,
        _reason: SessionStateChangeReason
      ) => {
        setSessionState(container.sessionState);
      },
    };
    return d;
  }, [setSessionState]);

  useEffect(() => {
    if (isPlatformWeb()) {
      authgearWeb.delegate = delegate;
    } else {
      authgearCapacitor.delegate = delegate;
    }

    return () => {
      if (isPlatformWeb()) {
        authgearWeb.delegate = undefined;
      } else {
        authgearCapacitor.delegate = undefined;
      }
    };
  }, [delegate]);

  const configure = useCallback(async () => {
    try {
      if (isPlatformWeb()) {
        await authgearWeb.configure({
          clientID: CLIENT_ID,
          endpoint: ENDPOINT,
          sessionType: "refresh_token",
          isSSOEnabled: false,
        });
      } else {
        await authgearCapacitor.configure({
          clientID: CLIENT_ID,
          endpoint: ENDPOINT,
        });
      }
      await postConfigure();
    } catch (e) {
      console.error("Authgear Configuration error:", e);
    } 
  }, [CLIENT_ID, ENDPOINT]);

  const postConfigure = useCallback(async () => {
    const sessionState = isPlatformWeb()
      ? authgearWeb.sessionState
      : authgearCapacitor.sessionState;

    // if user has an existing session, call SDK fetchUserInfo method to get the user's info and refresh access token when necessary
    if (sessionState === "AUTHENTICATED") {
      if (isPlatformWeb()) {
        await authgearWeb.fetchUserInfo();
      } else {
        await authgearCapacitor.fetchUserInfo();
      }
    }
  }, []);

  useEffect(() => {
    configure();
  }, []);

  const authenticate = useCallback(async (page: string) => {
    try {
      if (isPlatformWeb()) {
        authgearWeb.startAuthentication({
          redirectURI: REDIRECT_URI_WEB_AUTHENTICATE,
          page: page,
        });
      } else {
        const result = await authgearCapacitor.authenticate({
          redirectURI: REDIRECT_URI_CAPACITOR,
          page: page,
        });
      }
    } catch (e) {
      console.error("Authentication error:", e);
    } 
  }, []);

  const onClickAuthenticate = useCallback(
    (e: MouseEvent<HTMLIonButtonElement>, page: string) => {
      e.preventDefault();
      e.stopPropagation();

      authenticate(page);
    },
    [authenticate]
  );

  return (
    <>
      <div className="container">
        <h1>Welcome</h1>
        {!loggedIn ? (
          <IonButton
            className="button"
            onClick={(event) => {
              onClickAuthenticate(event, "login");
            }}
          >
            Login
          </IonButton>
        ) : (
          <div>
            <p>Welcome user</p>
          </div>
        )}
      </div>
    </>
  );
}
const Home: React.FC = () => {
  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Blank</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent fullscreen>
        <IonHeader collapse="condense">
          <IonToolbar>
            <IonTitle size="large">Blank</IonTitle>
          </IonToolbar>
        </IonHeader>
        <AuthenticationScreen />
      </IonContent>
    </IonPage>
  );
};

export default Home;

保存好您的工作,然后运行命令在 Web 上运行项目,或将其构建为 iOS 或 Android 应用。

点击登录按钮后,您应该能看到身份验证界面。不过,由于我们还没有处理重定向,此时还无法完成身份验证流程。

Step 6: Handle Redirect in App

身份验证流程结束时,用户会被重定向到您在 redirectURI 中指定的 URL。在这一步,我们将设置处理这些重定向 URI 所需的路由和代码。

要在 Web 上处理重定向,请在 src/pages/ 下新建文件 OAuthRedirect.tsx,并添加以下代码:

import { useCallback, useEffect } from "react";
import authgearWeb from "@authgear/web";
import { useIonRouter } from "@ionic/react";

export default function OAuthRedirect() {
  const router = useIonRouter();

  const finishAuthentication = useCallback(async () => {
    const CLIENT_ID = "<ClIENT_ID>";
    const ENDPOINT = "<AUTHGEAR_ENDPOINT>";

    try {
      await authgearWeb.configure({
        clientID: CLIENT_ID,
        endpoint: ENDPOINT,
        sessionType: "refresh_token",
      });
      await authgearWeb.finishAuthentication();
      router.push("/", "root", "replace");
    } catch (e) {
      console.error(e);
    }
  }, [router]);

  useEffect(() => {
    finishAuthentication();
  }, [finishAuthentication]);

  return (
    <div>
      Finishing authentication. Open the inspector to see if there is any error.
    </div>
  );
}

将上面代码中 CLIENT_ID 和 ENDPOINT 的值改为您 Authany 应用配置页面中的正确值。

现在打开 src/App.tsx,使用以下代码为 OAuthRedirect 创建一个新路由:

<Route exact path="/oauth-redirect">
    <OAuthRedirect />
</Route>

记得在 App.tsx 中导入 OAuthRedirect:

import OAuthRedirect from './pages/OAuthRedirect';

要在 Android 项目中处理重定向,请将以下代码添加到 android/app/src/main/AndroidManifest.xml 中:

<!-- Authgear SDK -->
<activity
    android:name="com.authgear.capacitor.OAuthRedirectActivity"
    android:launchMode="singleTask"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Configure data to be the exact redirect URI your app uses. -->
        <!-- NOTE: The redirectURI supplied in AuthenticateOptions has to match as well -->
        <data
            android:host="host"
            android:pathPrefix="/path"
            android:scheme="com.authgear.example.capacitor" />
    </intent-filter>
</activity>

至此,构建并运行项目后,您应该可以成功登录。

Step 7: Implement Logout

要实现登出功能,我们将添加一个登出按钮、一个 onClick 处理函数,以及一个调用 Authany SDK 的 logout() 方法的方法。

在 AuthenticationScreen 组件中 <p>Welcome user</p> 下方添加一个登出按钮:

<IonButton className="button" onClick={onClickLogout}>
    Logout
</IonButton>

接着,为 AuthenticationScreen 组件添加一个 logout() 方法:

const logout = useCallback(async () => {
  try {
    if (isPlatformWeb()) {
      await authgearWeb.logout({
        redirectURI: window.location.origin + "/",
      });
    } else {
      await authgearCapacitor.logout();
    }
  } catch (e) {
    console.error("Logout error:", e);
  } 
}, []);

最后,在 AuthenticationScreen 中实现 onClick 方法:

const onClickLogout = useCallback(
  (e: MouseEvent<HTMLIonButtonElement>) => {
    e.preventDefault();
    e.stopPropagation();

    logout();
  },
  [logout]
);

Step 8: Get UserInfo

Authany SDK 提供了一个 fetchUserInfo() 方法,可以返回当前用户的 User ID、邮箱、手机号等信息。在这一步,我们将演示如何在应用中调用 fetchUserInfo。

首先,在 AuthenticationScreen 组件中登出按钮下方添加一个 Fetch User Info 按钮:

<IonButton className="button" onClick={onClickFetchUserInfo}>
  Fetch User Info
</IonButton>

接着,为 AuthenticationScreen 组件添加一个 fetchUserInfo() 方法:

const fetchUserInfo = useCallback(async () => {
  try {
    const authgear = isPlatformWeb() ? authgearWeb : authgearCapacitor;
    const userInfo = await authgear.fetchUserInfo();
    alert(JSON.stringify(userInfo, null, 2)); // Keep for demonstration, but consider a better way to display info
  } catch (e) {
    console.error("FetchUserInfo error:", e);
  } 
}, []);

最后,添加处理 Fetch User Info 按钮点击事件的方法:

const onClickFetchUserInfo = useCallback(
  (e: MouseEvent<HTMLIonButtonElement>) => {
    e.preventDefault();
    e.stopPropagation();

    fetchUserInfo();
  },
  [fetchUserInfo]
);

Step 9: Open User Settings Screen

Authany 提供了一个默认的用户设置页面,用户可以在其中查看资料详情,并修改密码等安全设置。

为了让用户可以从应用中打开设置页面,首先在 AuthenticationScreen 组件中 Fetch User Info 按钮下方添加一个 User Settings 按钮:

<IonButton
    className="button"
    onClick={onClickUserSettings}
    >
    User Settings
</IonButton>

现在,在 AuthenticationScreen 组件中添加一个 openUserSettings() 方法:

const openUserSettings = useCallback(async () => {
  try {
    if (isPlatformWeb()) {
      authgearWeb.open(Page.Settings);
    } else {
      authgearCapacitor.open(Page.Settings);
    }
  } catch (e) {
    console.error("Error:", e)
  } 
}, []);

最后,为 User Settings 按钮实现 onClick 方法:

const onClickUserSettings = useCallback(
  (e: MouseEvent<HTMLIonButtonElement>) => {
    e.preventDefault();
    e.stopPropagation();

    openUserSettings();
  },
  [logout]
);

Step 10: Deploy app to mobile

要将应用部署到移动设备(例如 Android),请运行以下命令:

首先运行以下命令构建项目:

npm run build

然后使用以下命令将更改同步到移动端项目:

npx cap sync

您可以在 Android Studio 中打开 android 项目文件夹,或在 Xcode 中打开 ios 文件夹来运行项目。

您可以使用以下命令快速在 Android Studio 中打开项目:

npx cap open android

或者运行以下命令,在 Xcode 中打开 iOS 项目:

npx cap open ios

项目成功构建后,您就可以试用登录、注册、Fetch User Info 和登出按钮了。

Conclusion

Authany Capacitor SDK 让您在 Ionic 应用中使用 Authany 变得更加轻松,提供了许多便捷的方法和接口,方便 Ionic 应用与 Authany 服务交互。要了解更多关于该 SDK 的信息,请查看 SDK 参考文档。另外,您也可以查看 Authany Ionic SDK 示例应用的完整仓库。