SupabaseとRLSで安全なReact Nativeウェアラブルアプリを構築
安全なReact Nativeウェアラブルアプリを構築するためのフルスタックガイド。健康データをリアルタイムで同期し、Supabaseの行レベルセキュリティ(RLS)を使用して、ユーザーが自分の情報にのみアクセスできるように保護する方法を学びます。
このページの内容
ウェアラブル技術市场和健康技術市場は急成長しています。開発者として、これは大きなチャンスですが、敏感なユーザ健康データを保護するという重大な責任も伴います。健康アプリでのデータ漏洩は、単なる不便ではありません。それは深刻なプライバシー侵害です。
このチュートリアルでは、React NativeアプリケーションからSupabaseバックエンドへのフルスタックの安全なデータパイプラインを構築します。フィットネスウェアラブル(心拍数と歩数)のデータをシミュレートし、リアルタイムで同期します。秘密のソース是什么?Supabaseの強力な**Row Level Security(RLS)**を使用して、ユーザーが自分のデータだけを読み書きできるデータベースレベルのルールを作成し、アプリのセキュリティの基礎を構築します。
構築するもの: シミュレートされたウェアラブルデータが継続的に生成され、Supabaseデータベースに安全にアップロードされるユーザー認証付きのReact Nativeアプリ。リアルタイム表示で、ユーザーが同期中に自分のデータだけを確認できます。
前提条件:
- Node.js(LTSバージョン)とnpm/yarnがインストールされていること。
- テスト用のモバイルデバイスにExpo Goアプリがインストールされていること。
- 無料のSupabaseアカウント。
- React NativeとSQLの基本的な理解。
開発者にとってなぜ重要か: ユーザーデータの安全な取り扱いは必須です。このプロジェクトは、伝統的なバックエンドサーバーを管理する複雑さなしに、安全でリアルタイムのアプリケーションを構築するための堅牢でスケーラブルなパターンを教えてくれます。
問題の理解
典型的なクライアントサーバーモデルでは、/api/health-dataのようなAPIエンドポイントを持っているかもしれません。サーバーサイドのコードは、ログインしているユーザーがリクエストされたデータにアクセスする許可があるかどうかを確認する責任を負います。これは実行可能なソリューションですが、欠点もあります:
- 複雑さ:複数のエンドポイントで認可ロジックを記述して維持する必要があります。
- 漏洩の可能性:APIコードのバグ一つで、、あるユーザーのデータが別のユーザーに誤って公開される可能性があります。
- 多層防御:セキュリティ solely on APIに依存するのは危険です。APIキーが漏洩したら怎么办?
私たちのアプローチでは、セキュリティをデータベース自体に移します。Supabaseでは、データを守る門番のように機能するシンプルなSQLポリシーを作成できます。リクエストがフロントエンドを突破しても、RLSポリシーに違反するアクションはデータベースが拒否します。これは最も低く、最も根本的なレベルでのセキュリティです。
前提条件:ツールの設定
1. Supabaseプロジェクトの設定
- Supabaseダッシュボードにアクセスし、「新規プロジェクト」をクリックします。
- プロジェクトに名前を付け、強力なデータベースパスワードを設定します。自分に近いリージョンを選びます。
- プロジェクトが作成されたら、左サイドバーの「API」設定に移動します。プロジェクトURLと
anon公開キーが必要です。これらを近くに置いておいてください。
2. React Nativeプロジェクトの設定
ターミナルを開き、新しいExpoプロジェクトを作成します:
npx create-expo-app wearable-data-sync
cd wearable-data-sync
次に、Supabaseに接続するために必要な依存関係をインストールします:
npm install @supabase/supabase-js @react-native-async-storage/async-storage react-native-url-polyfill
@supabase/supabase-js: Supabase用の公式JavaScriptクライアント。@react-native-async-storage/async-storage: SupabaseがReact Nativeでユーザーセッションを永続化するために使用する、安全で非同期なストレージシステム。react-native-url-polyfill: SupabaseがReact Native環境で正しく機能するために必要な小さなポリフィル。
ステップ1:Supabaseバックエンドテーブルの作成
ここが健康データの住処です。
何をしているか
Supabase SQLエディターを使用してhealth_dataテーブルを作成します。心拍数、歩数の列、そしてもちろん、各エントリを特定のユーザーにリンクするuser_idを定義します。
実装
- Supabaseプロジェクトで「SQLエディター」に移動します。
- 「+ 新規クエリ」をクリックし、次のSQLを実行します:
-- 1. health_dataテーブルを作成
CREATE TABLE public.health_data (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
heart_rate INT,
steps INT
);
-- 2. テーブルでRow Level Security(RLS)を有効化
ALTER TABLE public.health_data ENABLE ROW LEVEL SECURITY;
動作の仕組み
- メトリクスを保存するためのシンプルなテーブルを作成します。
user_id列は、Supabase組み込みのauth.usersテーブルのidを参照する外部キーです。このデータベースレベルのリンクが、セキュリティポリシーを可能にします。ALTER TABLE ... ENABLE ROW LEVEL SECURITY;が重要なコマンドです。デフォルトでは、RLSが有効でポリシーが없는場合、テーブルへのすべてのアクセスが拒否されます。この「デフォルトで安全」アプローチにより、誤ったデータ公開を防ぎます。
ステップ2:React NativeをSupabaseに接続する
次に、新しいバックエンドと通信するようにモバイルアプリを設定します。
何をしているか
プロジェクトダッシュボードからのAPIキーを使用して、Supabaseクライアントを初期化するヘルパーファイルを作成します。
実装
- React Nativeプロジェクトのルートに
lib/supabase.jsというファイルを作成します。 - プレースホルダーを実際のSupabase URLとanonキーに置き換えて、次のコードを追加します。
// lib/supabase.js
import 'react-native-url-polyfill/auto';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = 'YOUR_SUPABASE_URL';
const supabaseAnonKey = 'YOUR_SUPABASE_ANON_KEY';
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
storage: AsyncStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
});
動作の仕組み
このコードは、単一の再利用可能なsupabaseクライアントインスタンスを作成します。セッションの永続化にAsyncStorageを使用するように設定し、これはReact Nativeの標準です。先頭でreact-native-url-polyfillをインポートして互換性を確保します。
ステップ3:ユーザー認証の実装
ユーザーがデータを同期するには、まずログインする必要があります。
何をしているか
サインインとサインアップのためのシンプルなUIを作成し、ユーザーのセッション状態管理工作を行います。
実装
- 新しいファイル
src/components/Auth.jsを作成します。 - 基本的なメール/パスワード認証フォームのコードを追加します。
// src/components/Auth.js
import React, { useState } from 'react';
import { Alert, StyleSheet, View, TextInput, Button, Text } from 'react-native';
import { supabase } from '../../lib/supabase';
export default function Auth() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
async function signInWithEmail() {
setLoading(true);
const { error } = await supabase.auth.signInWithPassword({
email: email,
password: password,
});
if (error) Alert.alert(error.message);
setLoading(false);
}
async function signUpWithEmail() {
setLoading(true);
const { error } = await supabase.auth.signUp({
email: email,
password: password,
});
if (error) Alert.alert(error.message);
setLoading(false);
}
return (
<View style={styles.container}>
<Text style={styles.header}>Health Sync</Text>
<TextInput
style={styles.input}
onChangeText={(text) => setEmail(text)}
value={email}
placeholder="[email protected]"
autoCapitalize={'none'}
/>
<TextInput
style={styles.input}
onChangeText={(text) => setPassword(text)}
value={password}
secureTextEntry={true}
placeholder="Password"
autoCapitalize={'none'}
/>
<View style={styles.buttonContainer}>
<Button title="Sign in" disabled={loading} onPress={() => signInWithEmail()} />
</View>
<View style={styles.buttonContainer}>
<Button title="Sign up" disabled={loading} onPress={() => signUpWithEmail()} />
</View>
</View>
);
}
// Add some basic styling
const styles = StyleSheet.create({ /* ... */ });
- 次に、
App.jsを更新してセッションを管理します。
// App.js
import React, { useState, useEffect } from 'react';
import { View } from 'react-native';
import { supabase } from './lib/supabase';
import Auth from './src/components/Auth';
import HealthDashboard from './src/components/HealthDashboard'; // 次にこれを作成します
export default function App() {
const [session, setSession] = useState(null);
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
});
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);
return (
<View>
{session && session.user ? <HealthDashboard key={session.user.id} session={session} /> : <Auth />}
</View>
);
}
動作の仕組み
App.jsは-routerになります。アクティブなセッションをチェックします。ユーザーがログインしている場合はHealthDashboardを表示し、そうでない場合はAuthコンポーネントを表示します。onAuthStateChangeリスナーは、ユーザーがサインインまたはサインアウト的时候会動的にUIを更新します。✨
ステップ4:セキュアな同期パイプラインの構築
これがアプリケーションの中核です。ウェアラブルのデータをシミュレートし安全に同期します。
何をしているか
3つのことを行うHealthDashboardコンポーネントを作成します:
- 数秒ごとに新しい心拍数と歩数データをシミュレートします。
- この新しいデータを
health_dataテーブルに挿入します。 - そのテーブルからのリアルタイム更新をサブスクライブしてデータを表示します。
実装
// src/components/HealthDashboard.js
import React, { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet, FlatList, Alert } from 'react-native';
import { supabase } from '../../lib/supabase';
const HealthDashboard = ({ session }) => {
const [healthData, setHealthData] = useState([]);
const [loading, setLoading] = useState(false);
// ** 1. 初期データを取得し、リアルタイムサブスクリプションを設定 **
useEffect(() => {
getHealthData();
const channel = supabase
.channel('health_data_changes')
.on('postgres_changes', { event: '*', schema: 'public', table: 'health_data' }, (payload) => {
// 新しいデータが到着すると、リストの先頭に追加
setHealthData((currentData) => [payload.new, ...currentData]);
})
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, []);
// ** 2. ウェアラブルをシミュレートしデータを同期 **
useEffect(() => {
const interval = setInterval(() => {
syncNewData();
}, 5000); // 5秒ごとに新しいデータを同期
return () => clearInterval(interval);
}, []);
const getHealthData = async () => {
setLoading(true);
const { data, error } = await supabase
.from('health_data')
.select('*')
.order('created_at', { ascending: false }); // 最も新しい順
if (error) {
Alert.alert('データ取得エラー', 'RLSポリシーを設定しましたか?');
console.error('Fetch error:', error.message);
} else {
setHealthData(data);
}
setLoading(false);
};
const syncNewData = async () => {
const newHeartRate = Math.floor(Math.random() * (120 - 60 + 1)) + 60; // ランダムな心拍数 60-120
const newSteps = Math.floor(Math.random() * 50) + 10; // ランダムな歩数 10-50
const { error } = await supabase
.from('health_data')
.insert({
user_id: session.user.id,
heart_rate: newHeartRate,
steps: newSteps,
});
if (error) {
Alert.alert('同期エラー', error.message);
}
};
return (
<View style={styles.container}>
<Text style={styles.header}>ようこそ、{session.user.email}</Text>
<Text style={styles.subHeader}>リアルタイム健康データ:</Text>
<FlatList
data={healthData}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.dataRow}>
<Text>❤️ {item.heart_rate} bpm</Text>
<Text>👟 {item.steps} 歩</Text>
<Text style={styles.timestamp}>{new Date(item.created_at).toLocaleTimeString()}</Text>
</View>
)}
/>
<Button title="サインアウト" onPress={() => supabase.auth.signOut()} />
</View>
);
};
// Add styles
const styles = StyleSheet.create({ /* ... */ });
export default HealthDashboard;
動作の仕組み
- データシミュレーション:
useEffectフック内のsetIntervalが5秒ごとにsyncNewDataを呼び出し、ランダムなデータを生成します。 - データ挿入:
syncNewDataはsupabase.from('health_data').insert(...)を使用して新しいデータをアップロードします。重要なことに、ログインしているユーザーとデータを関連付けるためにsession.user.idを含めます。 - リアルタイムサブスクリプション:最初の
useEffectはsupabase.channel(...)を使用してサブスクリプションを設定します。health_dataテーブルへのINSERTイベントをリッスンします。変更が検出されるとペイロードを使用してコンポーネントの状態を更新し、UIが即座に再レンダリングされます。
現時点でアプリを実行すると、サインアップできますが、データを取得または挿入することはできません! エラーアラートが表示されます。これはRLSが有効になっているが、「許可」ポリシーをまだ定義していないためです。データベースは正しくすべてのリクエストをブロックしています。
ステップ5:行レベルセキュリティポリシーの実装
ここに権限を付与し、セキュリティモデルを実施します。
何をしているか
Supabaseダッシュボードで2つのシンプルなSQLポリシーを記述します:
- ユーザーが自分のデータだけを読み取れる
SELECTポリシー。 - ユーザーが自分のレコードだけを作成できる
INSERTポリシー。
実装
Supabaseダッシュボードの「認証」→「ポリシー」セクションに戻り、health_dataテーブルを選択して「+ 新規ポリシー」をクリックします。
1. SELECT(読み取り)ポリシー
- 「新規ポリシーを作成」をクリックします。
- 「全員の読み取りアクセスを有効化」の「スムーズに開始」テンプレートを選びます。
- ポリシー名をに変更します:
ユーザーは自分の健康データのみ表示できる SELECTが許可された操作であることを確認します。USING式の下で、trueを以下に変更します:
auth.uid() = user_id
- 「レビュー」をクリックして「ポリシーを保存」をクリックします。
2. INSERT(作成)ポリシー
- もう一度「+ 新規ポリシー」をクリックします。
- 「認証済みユーザーのみ.insertアクセスを有効化」のテンプレートを選びます。
- ポリシー名をに変更します:
ユーザーは自分の健康データを挿入できる INSERTが許可された操作であることを確認します。WITH CHECK式の下で、trueを以下に変更します:
auth.uid() = user_id
* 「レビュー」をクリックして「ポリシーを保存」をクリックします。
### 動作の仕組み
* `auth.uid()`:これは現在のリクエストを行っている認証済みユーザーの一意のIDを返す特別なSupabase関数です。
* **SELECTポリシー**:`USING (auth.uid() = user_id)`句は、すべての`SELECT`クエリに mandatory な`WHERE`フィルターのように機能します。PostgreSQLに「リクエストしているユーザーのIDと`user_id`列が一致する行のみを返す」と指示します。
* **INSERTポリシー**:`WITH CHECK (auth.uid() = user_id)`句は検証ルールのように機能します。PostgreSQLに「この新しい行を受け入れる前に、挿入されている`user_id`が挿入を実行しているユーザーのIDと同じであることを確認する」と指示します。これにより、ユーザーが악의的に他のユーザーに代わってデータを挿入することを防ぎます。
React Nativeアプリを再起動します。新規ユーザーとしてサインアップ(またはサインイン)します。ダッシュボードが表示され、5秒ごとに新しいデータポイントが表示されます!2番目のユーザーアカウントを作成してログインすると、完全分开の空のデータリストが表示され、セキュリティポリシーが正常に機能していることが証明されます。
## 結論
安全なフルスタックのデータパイプラインを正常に構築しました。ユーザー認証を处理し、Supabaseバックエンドにリアルタイムでデータを同期できるReact Nativeアプリが手に入りました。最も重要的是、行レベルセキュリティでデータをロックダウンし、直接データベースアクセスがあっても、ユーザーの機密健康情報が分離され保護されていることを確認しました。
このパターンは、堅牢でスケーラブル、かつ安全なアプリケーションを構築するための基盤です。ここから、プロジェクトを多くの方向发展させることができます。
**読者向けの次のステップ:**
* **実際のウェアラブル統合**:シミュレーターを`react-native-health`や`react-native-wearables`などのライブラリに置き換えて、Apple HealthやGoogle Fitから実際のデータを取得します。
* **データ可視化**:チャートライブラリを使用して、時間の経過に伴うユーザーの健康データのグラフを作成します。
* **オフラインサポート**:SQLiteなどのローカルデータベースと同期エンジンを使用したオフラインファースト戦略を実装して、ユーザーがインターネット接続を持っていない場合に対応します。
## リソース
* **Supabase公式ドキュメント**:[supabase.com/docs](https://supabase.com/docs)
* **Supabase RLSドキュメント**:[Supabase Row Level Security Docs](https://supabase.com/docs/guides/auth/row-level-security)
* **Expoドキュメント**:[docs.expo.dev](https://docs.expo.dev/)
* **React Native Health**:[GitHub Repository](https://github.com/agencyenterprise/react-native-health)
---
## クイックリファレンス
| 側面 | 主要情報 |
|--------|----------------|
| **トピック** | 開発 |
| **関連性** | 患者教育と認識 |
| **アクション** | 医療提供者と話し合う |
| **頻度** | ケアチームが推奨するごと |
| **証拠** | 現在の臨床ガイドラインに基づく |
## よくある質問
**Q: これが自分に該当する怎么看才知道か?**
A: 個人的な既往歴と家族歴について医疗提供者と話し合いただき、個人のリスク因子を評価し、適切な次のステップを決定してください。
**Q: 医師に何と聞けばいいのか?**
A: 良い質問には次のようなものあります:「私の結果はどういう意味ですか?」「私のオプションは何ですか?」「どのような生活習慣改变有帮助ですか?」「いつフォローアップすべきですか?」
**Q: 帮助になれる生活習慣改变はありますか?**
A: ほとんどの健康状態は、基本的な対策から好处を得られます:バランスの取れた栄養、定期的な運動(週150分以上)、質の良い睡眠(7〜9時間)、ストレス管理、タバコの回避。
## 医療に関する免責事項
この記事は教育的目的是のみで、医学的アドバイスを提供ものではありません。健康、治療、またはケアに関する決定を下す前には、必ず資格のある医療提供者とご相談ください。個人の状況は異なり、一个人に適切なことが別の人に適切とは限りません。
救急車を要する医療上の緊急事態が発生した場合は、直ちに119(またはお住まいの地域の緊急番号)に電話してください。