React Nativeアプリに認証を追加するには?

リアクト・ネイティブ(エキスポ)認証カバー

認証は、ほとんどすべてのアプリの核となる要素である。

この実践的な記事では、React Native認証を始めるためのステップバイステップのガイドを提供します。

その上で、認証の基本、認証との比較、認証の流れ、認証を使うメリットについて説明する。

認証とは何か?

認証とは、誰かの身元を確認するプロセスである。一般的に、認証には3つの方法がある:

  1. 知っていること(パスワードや暗証番号など)
  2. 持っているもの(携帯電話、鍵など)
  3. あなたが何者かであること(例:指紋、虹彩)

ほとんどのアプリケーションは最初のオプションを使用するが、多くのアプリケーションは複数のオプションを組み合わせてセキュリティレベルを高めている。このコンセプトは多要素認証(MFA)と呼ばれる。

認証フロー

一般的な認証フローは次のように動作する:

  1. ユーザーはパスワードをサーバーに送信する。
  2. サーバーはパスワードをハッシュし、データベース内のパスワードハッシュと比較する。
  3. ハッシュが一致すれば、サーバーはセッションを作成し、セッショントークンをユーザーに送信する。一方、ハッシュが一致しない場合はエラーが発生する。
  4. その後、ユーザーはリクエストごとにセッション・トークンを使用する。各リクエストで、サーバはトークンが存在し有効かどうかをチェックします。

上記の流れは、いわゆるトークン認証である。その他の認証方式としては、JSON Web Token(JWT)、ベーシックアクセス認証ソーシャルログインなどがある。

認証と認可

認証はユーザーの身元を確認する行為であり、認可はユーザーがアクションを実行するのに十分な権限を持っているかどうかを確認する行為である。

認可モデルには、強制アクセス制御(MAC)、裁量アクセス制御(DAC)、役割ベースの認可などがある。

認証のメリット

モバイルアプリで認証を使用するメリットをいくつか見てみよう。

セキュリティ

認証は、不正アクセスからアプリを保護し、正当なユーザーだけがサービスにアクセスできることを保証します。

これにより、データ漏洩やサイバー攻撃を防止し、アプリ環境の安全性と信頼性を保つことができます。

パーソナライゼーション

認証は、アプリ内でパーソナライズされたユーザー体験を可能にする。ユーザーがログインすると、ニーズに合わせてカスタマイズされた設定、プリファレンス、レコメンデーションにアクセスできる。

このパーソナライゼーションは、アプリをより魅力的でユーザーフレンドリーなものにします。より適切で楽しい体験を提供することで、ユーザーの囲い込みに役立ちます。

法的要件

一部の国の法律では、ユーザーの身元を確認することが義務付けられています。その一例がKYC(Know Your Customer)です。

ほぼすべての金融系アプリに義務付けられています。これらの規制に準拠するため、またはユーザーの個人情報を保護するために、適切な認証システムを用意する必要があります。

React Nativeアプリに認証を追加するには?

このパートでは、React Native(Expo)アプリにBack4app認証を追加するためのステップバイステップのガイドを提供します。

前提条件

  • サービスとしてのバックエンド(BaaS)の基本的な理解
  • JavaScriptのIDE、Node.js、モバイルエミュレータまたは物理デバイス
  • JavaScriptコードの読み書きができる
  • React NativeとExpoの使用経験
  • 無料のBack4appアカウント

Back4appとは?

Back4appは、最高のオープンソースのBaaS(Backend as a Service)ソリューションの1つです。2015年以来、成熟した信頼性の高いプラットフォームです。

主な機能には、リアルタイム・データベース、クラウド・コード機能、RESTful/GraphQL API自動生成などがある!

Back4appには、使いやすく直感的な管理ダッシュボードと、上級ユーザー向けのコマンドラインインターフェイス(CLI)があります。

また、JavaScript、PHP、Flutter、Dartなど、最も人気のあるプログラミング言語やフレームワーク用のSDKも提供している。

Back4appには無料版もあります。無料版はテストやプロトタイピングに最適で、以下の機能が含まれています:

  • 25,000円/月のリクエスト
  • 250MBのデータストレージ
  • 1 GBのデータ転送
  • 1 GBのファイルストレージ

なぜBack4appを使うのですか?

  • ソーシャル認証に対応
  • ほとんどのプログラミング言語とフレームワーク用のSDKを提供
  • 簡単なセットアップと使用
  • 優れたカスタマーサポート

Back4app認証とFirebase認証の比較については、React Firebase認証の究極ガイドをご覧ください。

プロジェクト紹介 – React Native認証を始めよう

私たちは、Back4app認証を使用した本番環境対応のReact Nativeアプリケーションを構築します。このアプリでは、ユーザーが登録、ログイン、プロフィールの管理を行うことができます。

その上、アプリには認証ユーザー用と未認証ユーザー用の2つのタブナビゲーションがある。

最終的にはこのようになる:

Back4app認証 Reactネイティブアプリ

コーディングを始めよう!

バックエンド(Back4app)

このセクションでは、Back4appアプリを作成し、デフォルトのユーザーモデルを拡張し、バックエンドに接続するために必要なAPIキーを取得する。

アプリ作成

まず、Back4appアカウントにログインするか、まだお持ちでない場合はアカウントを作成してください。

ログインすると、アプリ一覧にリダイレクトされます。新しいアプリを作成するには、”Build new app “をクリックしてください。

Back4app アプリ一覧

Back4appプラットフォームでは、BaaS(Backend as a Service)とCaaS(Containers as a Service)の2種類のアプリをデプロイできる。認証はBaaSに含まれているので、それを選択する。

Back4app Backend as a Service セレクト

次に、アプリにわかりやすい名前をつけ、データベースはNoSQLのままにして、「Create」をクリックする。

Back4appアプリの設定

プラットフォームがアプリを作成するまでおよそ2分待つ。Back4appはアプリレイヤーの作成からデータベースの設定、セキュリティ、スケーリングなど全てを行います。

完了すると、データベースのインターフェイスにリダイレクトされます。

Back4appデータベースビュー

データベースの修正

次に、データベース・クラスについて説明しよう。

すべてのBack4appデータベースクラスには、以下のデフォルトフィールドがあります:

+-----------+------------+-----------------------------------------+
| Data type | Name       | Description                             |
+-----------+------------+-----------------------------------------+
| String    | objectId   | Object's unique identifier              |
+-----------+------------+-----------------------------------------+
| Date      | createdAt  | Date of object creation                 |
+-----------+------------+-----------------------------------------+
| Date      | updatedAt  | Date of object's last update            |
+-----------+------------+-----------------------------------------+
| ACL       | ACL        | Access Control List (security features) |
+-----------+------------+-----------------------------------------+

そして、Userクラスにはいくつかの追加フィールドがある:

+-----------+----------------+--------------------------+----------+
| Data type | Name           | Default value            | Required |
+-----------+----------------+--------------------------+----------+
| String    | username       |                          | yes      |
+-----------+----------------+--------------------------+----------+
| String    | email          |                          | no       |
+-----------+----------------+--------------------------+----------+
| Boolean   | emailVerified  | false                    | no       |
+-----------+----------------+--------------------------+----------+
| String    | password       |                          | yes      |
+-----------+----------------+--------------------------+----------+
| Object*   | authData       | {}                       | yes      |
+-----------+----------------+--------------------------+----------+

デフォルトのBack4appユーザークラスは、ほとんどのユースケースに適しています。とはいえ、これを拡張するのは簡単だ。その方法を示すために、経歴(bio)フィールドを追加してみましょう。

まず、データベーステーブルの上にある “+列 “ボタンをクリックします。

Back4app データベース カラムの追加

カラム作成フォームで、データ型として「String」を選択し、名前を「bio」に設定し、必須にして「Add」をクリックする。

Back4app データベースフィールドの追加

これで、Back4appのデータベースクラスがどのように動作し、どのようにユーザーモデルを拡張するかがわかりました。

APIキー

フロントエンドからバックエンドに接続するには、アプリのAPIキーを取得する必要がある。

取得するには、Back4appアプリに移動し、サイドバーの “Security & Keys “を選択してください。クライアントキー “と “JavaScriptキー “に注意してください。

Back4app APIキー

バックエンド側からは以上だ。

フロントエンドReact Native)

このセクションでは、Expoアプリを作成し、コンポーネント・ライブラリをインストールし、ナビゲーションを設定し、スクリーンを作成し、最後にフロントエンドとバックエンドを接続します。

アプリ作成

React Nativeアプリを作成するには、create-expo-appユーティリティを使用する。このユーティリティは、ディレクトリ構造の生成やTypeScriptの設定など、React Nativeアプリの作成を簡素化する。

まず、以下のコマンドを実行する:

npx create-expo-app@latest back4app-expo-auth
cd back4app-expo-auth

このコマンドはcreate-expo-appもインストールする。

ブートストラップされたプロジェクトは次のようなディレクトリ構造になっていることに気づくだろう:

back4app-expo-auth/
├── app                 - Layouts, screens
├── assets              - Static assets (e.g. images, videos, fonts)
├── components          - Reusable components used through the app
├── constants           - Static variables & configurations
├── hooks               - Custom React hooks
├── scripts             - Development scripts
├── app.json            - Expo configuration & metadata
├── expo-env.d.ts       - Expo TypeScript declarations
├── ...    

開発サーバーを起動します:

$ npx expo start

最後に、Aボタンを押してAndroidエミュレーターでアプリを開きます。または、iOSエミュレーターやiOSデバイスを使用することもできます。アプリが開いたら、デフォルトのExpo画面が表示されるはずです。

万博デフォルト画面

React Native Paper

UI開発プロセスを簡素化するため、React Native Paperを使用する。

React Native Paperは、React Nativeアプリケーション用の使いやすく高品質なコンポーネント・ライブラリです。ほとんどすべてのユースケースをカバーする、多くのプリメイドコンポーネントを提供します。

NPM経由でインストールすることから始めよう:

$ npm install react-native-paper react-native-safe-area-context 

iOS用にビルドする場合は、ライブラリーのネイティブ部分もリンクする必要がある:

npx pod-install

次に、未使用のコンポーネントを本番環境に含めないようにBabelを設定する:

// babel.config.js

module.exports = function(api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    env: {
      production: {
        plugins: ["react-native-paper/babel"],
      },
    },
  };
};

次に、app/_layout.tsxに移動し、PaperProviderでアプリをラップする:

// app/_layout.tsx

// ...

export default function RootLayout() {

  // ...

  return (
    <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
      <PaperProvider>
        <Stack>
          <Stack.Screen name="index" options={{headerShown: false}}/>
          <Stack.Screen name="(auth)" options={{headerShown: false}}/>
          <Stack.Screen name="(tabs)" options={{headerShown: false}}/>
          <Stack.Screen name="+not-found"/>
        </Stack>
      </PaperProvider>
    </ThemeProvider>
  );
}

ファイル上部のインポートをお忘れなく:

import {PaperProvider} from "react-native-paper";

これでコンポーネント・ライブラリのインストールは完了だ。

コンテナ

もうひとつ、Containerコンポーネントを作成します。このコンポーネントはすべての画面で使用され、コンテンツが画面の端に行かないように、すべての側面にマージンを追加します。

componentsフォルダにContainer.tsxファイルを作成します:

// components/Container.tsx

import React from "react";
import {View} from "react-native";

export type ContainerProps = {
  children: React.ReactNode;
}

export function Container({children}: ContainerProps) {
  return (
    <View style={{margin: 12}}>
      {children}
    </View>
  );
}

ナビゲーションと画面

プロジェクト紹介で述べたように、私たちのアプリには2つのナビゲーション・タブがあります。

一つは認証されたユーザー用で、もう一つは認証されていないユーザー用です。認証されたタブでは、ユーザーは自分のプロフィールを管理することができ、もう一方ではログインまたはアカウントを作成することができます。

そのためにはまず、以下のようなディレクトリ構造を作る:

app/
├── (auth)/
│   ├── _layout.tsx
│   ├── login.tsx
│   └── register.tsx
├── (tabs)/
│   ├── _layout.tsx
│   └── profile.tsx
├── +html.tsx
├── +not-found.tsx
├── _layout.tsx
└── index.tsx

エキスポ・ルーターの詳細については、公式ドキュメントをご覧ください。

Expoのレイアウトには、一般的に多くの定型的なコードが含まれている。この記事をコードで溢れさせたくないので、ファイルの内容はGitHubから取得してほしい:

  1. (auth)/_layout.tsx
  2. (tabs)/_layout.tsx
  3. _layout.tsx

続けて、index.tsxに以下のように記述する:

// app/index.tsx

import React, {useEffect} from "react";
import {ActivityIndicator} from "react-native-paper";
import {useRouter} from "expo-router";
import {View} from "react-native";

export default function IndexScreen() {

  const router = useRouter();
  const isAuthenticated = true;

  useEffect(() => {
    setTimeout(() => {
      if (isAuthenticated) {
        router.push("profile");
      } else {
        router.push("login");
      }
    }, 1000);
  }, []);

  return (
    <View style={{
      flex: 1,
      justifyContent: "center",
      alignItems: "center",
    }}>
      <ActivityIndicator
        size="large"
        animating={true}
      />
    </View>
  );
}

index.tsxファイルはアプリのエントリー・ポイントです。この中で、ユーザーが認証されているかどうかをチェックし、それに応じてリダイレクトします。今のところ、リダイレクトはisAuthenticated変数に基づいています。

次に、画面のコードを取得する:

  1. app/(auth)/login.tsx
  2. app/(auth)/register.tsx
  3. app/(tabs)/profile.tsx

画面のコードはとてもシンプルだ。これはReact Nativeのコードで、React Native Paperを使ってフォームやその他のUIを作成している。また、useState()useEffect()といった基本的なReactフックも利用している。

Parse SDK

バックエンドへの接続にはParse SDKを使用します。SDKはデータの保存、操作、ユーザー認証、その他の機能を提供します。

まず、NPM経由でインストールする:

$ npm install parse @react-native-async-storage/async-storage --save
$ npm install --save-dev @types/parse

また、@react-native-async-storage/async-storageパッケージをインストールし、アプリが終了したときにユーザー・セッションを永続化するようにした。これがないと、ユーザーはアプリケーションを開くたびに認証しなければならない。

次に、プロジェクト・ルートに.envファイルを次のように作成する:

EXPO_PUBLIC_APPLICATION_ID=<your-back4app-application-id>
EXPO_PUBLIC_JAVASCRIPT_KEY=<your-back4app-client-key>

確実に交換<your-back4app-application-id>する と<your-back4app-client-key> をバックエンドのセクションで取得した API キーに置き換えてください。

_layout.tsxでParseを初期化する:

// app/_layout.tsx

// ...
import Parse from "parse/react-native.js";
import AsyncStorage from "@react-native-async-storage/async-storage";
import ParseContext from "@/context/parseContext";

Parse.setAsyncStorage(AsyncStorage);
Parse.initialize(
  process.env.EXPO_PUBLIC_APPLICATION_ID ?? "",
  process.env.EXPO_PUBLIC_JAVASCRIPT_KEY ?? "",
)
Parse.serverURL = "https://parseapi.back4app.com/";

async function testParse() {
  try {
    const testMessage = new Parse.Object("TestMessage");
    testMessage.set("message", "Hello, World!");
    await testMessage.save();
  } catch (error) {
    console.log("Error saving the test message: ", error);
  }
}
testParse().then(() => console.log("Successfully connected to Parse!"));

// ...

また、データベースに “Hello, world!”メッセージを追加することで、Back4appへの接続をテストするtestParse()関数も用意しました。Expoサーバーを再起動し、エミュレーターでアプリを実行することで、接続がうまくいくことを確認してください。

Back4app データベーステストメッセージ

アプリのデータベース・ビューに移動し、メッセージが表示されているか確認してください。

Error: crypto.getRandomValues() not supported “というエラーが表示された場合。以下の依存関係をインストールしてください:

npm install react-native-get-random-values --save
npm i --save-dev @types/react-native-get-random-values

次に、_layout.tsxの先頭(Parseをインポートする前)にimportを追加します:

import "react-native-get-random-values";

開発サーバーを再起動すれば、すべてうまくいくはずだ。

すべての画面でParseインスタンスを利用できるようにするために、React Contextを使ってインスタンスを渡します。

まず、コンテキスト・ディレクトリを作成し、その中に以下のparseContext.tsファイルを置く:

import {createContext} from "react";

const ParseContext = createContext<typeof Parse | null>(null);

export default ParseContext;

そして、Parseインスタンスを渡しながら、アプリ全体をこれでラップする:

// app/_layout.tsx

// ...

return (
  <ParseContext.Provider value={Parse}>
    {/* ... */}
  </ParseContext.Provider>
)

データ検索と操作

最後のセクションでは、Parse SDKを使ってユーザーを認証し、ユーザー情報を取得します。

すべての画面で同じコードを繰り返す代わりに、useParseフックを作成します。このフックは、コンテキストからParseインスタンスを取得し、ユーザー情報を取得し、すべての準備ができたら更新をトリガーします。

コンテキスト・フォルダーに useParse.tsというファイルを新規作成する:

// context/useParse.ts

import {useContext, useEffect, useState} from "react";
import ParseContext from "@/context/parseContext";

export function useParse() {
  const parse = useContext(ParseContext) as typeof Parse;
  const [parseUser, setParseUser] = useState<Parse.User | null>(null);
  const [isParseLoaded, setIsParseLoaded] = useState(false);

  useEffect(() => {
    (async () => {
      try {
        setParseUser(await parse.User.currentAsync());
      } catch (e) {
        console.error(e);
      } finally {
        setIsParseLoaded(true);
      }
    })();

  }, []);

  return {parse, parseUser, isParseLoaded};
}

次にindex.tsxを修正し、isAuthenticatedを実際のセッション・チェックに置き換える:

// app/index.tsx

// ...
import {useParse} from "@/hooks/useParse";

export default function IndexScreen() {

  const router = useRouter();
  const {parse, parseUser, isParseLoaded} = useParse();

  useEffect(() => {
    if (!isParseLoaded) return;
    (async () => {
      if (parseUser) {
        console.log("User is authenticated!");
        console.log(parseUser.toJSON());
        router.replace("/profile");
      } else {
        console.log("User is not authenticated.");
        console.log({});
        router.replace("/(auth)/login");
      }
    })();
  }, [isParseLoaded]);

  return (
    // ...
  );
}

次に、ユーザーをログインさせるためにlogin.tsxを修正する:

// app/(auth)/login.tsx

// ...
import {useParse} from "@/hooks/useParse";

export default function LoginScreen() {

  const router = useRouter();
  const {parse, parseUser, isParseLoaded} = useParse();

  // ...

  const onLogin = async () => {
    // ...
    try {
      await parse.User.logIn(username, password);
      router.push("/(tabs)/profile");
    } catch (error: any) {
      setError(error.message);
    }
  }

  return (
    // ...
  );
}

次にregister.tsxを login.tsxと同様に修正するが、今度はTODOを次のように置き換える:

// app/(auth)/register.tsx

try {
  const user = await parse.User.signUp(username, password, undefined, undefined);
  user.setEmail(email);
  await user.save();
  router.replace("/(tabs)/profile")
} catch (error: any) {
  setError(error.message);
}

最後にプロファイル情報を表示するためにprofile.tsxを修正する:

// app/(tabs)/profile.tsx

// ...
import {useParse} from "@/hooks/useParse";

export default function IndexScreen() {

  const router = useRouter();
  const {parse, parseUser, isParseLoaded} = useParse();

  // ...

  useEffect(() => {
    if (!parseUser) return;
    setBio(parseUser.get("bio") || "");
  }, [parseUser]);

  const onSave = async () => {
    if (!parseUser) return;
    parseUser.set("bio", bio);
    try {
      await parseUser.save();
      setSuccess("Bio saved successfully.");
    } catch (error: any) {
      setError(error.message);
    }
  }

  const onLogout = async () => {
    router.replace("/(auth)/login");
    await parse.User.logOut();
  }

  return (
    <Container>
      {!isParseLoaded ? (
        <ActivityIndicator
          size="large"
          animating={true}
        />
      ) : (
        <>
          {/* ... */}
        </>
      )}
    </Container>
  );
}

リターン・コールのTODOを変更することを忘れないでください。parseUser!.getUsername()}と {parseUser!.getEmail()}を使用してください。

この時点で、アプリは完全に動作しているはずです。開発サーバーを再起動し、アカウントを作成し、ログインし、ログアウトし、バイオを変更してテストする。最後に、変更がデータベースに反映されていることを確認する。

結論

結論として、認証とは何か、その利点は何か、そして認証との比較は何か、おわかり いただけたと思う。

さらに、Back4app認証を設定し、React Native(Expoベース)アプリケーションと統合する方法を学びました。

今後のステップ

  1. GoogleFacebookAppleソーシャル認証について学ぶ
  2. リアルタイム管理パネルならBack4app Admin Appが便利です。

最終的なソースコードはGitHubで公開されている。


Leave a reply

Your email address will not be published.