Zenn
🍍

expoでアプリを初回起動時に裏で勝手にユーザー登録をする

2025/03/27に公開

はじめに

アプリでユーザーごとのリソースを管理したいが、メールアドレスとパスワードを入力させるのは手間だから自動で作成できるように実装してみました。
バックエンドはsupabaseを使っています。

手順としては以下です。

  1. パスワードを日付等を元に生成する。
  2. ユーザー登録apiに1で作成したパスワードを併せてリクエストする。
  3. 1と2で返ってきたidをexpoのsecureStoreに保存する。

フロントのコード

パスワード生成にはブルートフォース攻撃等の推測ができないようにrandomUUIDを使っています。
それとモバイルに保存する方法としてexpoのsecureStoreを使うかreact-nativeのasyncStorageを使うか二つがあるみたいですが、機密情報にはsecureStoreを使用し、機密でない情報にはasyncStorageを使用するのが普通らしいです。

import { createUser, fetchUser } from "@/apis/user";
import * as Device from "expo-device";
import * as SecureStore from "expo-secure-store";
import React, { createContext, useContext, useEffect, useState } from "react";

export const UserProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const [user, setUser] = useState<User>();
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const initializeUser = async () => {
      setIsLoading(true);
      setError(null);

      try {
        const storedUserId = await SecureStore.getItemAsync("zenn_userId");
        // すでにユーザー情報が保存されていれば、それを使いfetchする。
        if (storedUserId) {
          const userData = await fetchUser(storedUserId);
          setUser(userData);
        } else {
        // ユーザー情報が保存されていない場合は、登録から始める。
          const securePassword = crypto.randomUUID();
          const newUserData = {
            password: securePassword,
          };

          const result = await createUser(newUserData);

          if (result) {
            await SecureStore.setItemAsync("zenn_userId", result.id);
            await SecureStore.setItemAsync("zenn_password", securePassword);
            const userData = await fetchUser(result.id);
            setUser(userData);
          } else {
            throw new Error("ユーザー作成に失敗しました");
          }
        }
        setIsLoading(false);
      } catch (err: any) {
        console.error("ユーザー初期化エラー:", err);
        setError(err.message || "ユーザーの初期化中にエラーが発生しました");
      }
    };

    initializeUser();
  }, []);

  const value = {
    user,
    isLoading,
    error,
  };

  return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};

バックエンドのコード

パスワードはハッシュ化して保存します。ハッシュ化についてはaiにまるまる聞いたのですが、記事を調べてみるとセキュリティ的に良さげなコードになってるっぽくてさすがだなーと思いました。

@app.route('/user/create', methods=['POST'])
def create_users():
    try:
        data = request.json
        password = data.get('password')
        hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')

        response = supabase.table('user').insert({"password": hashed_password}).execute()
        
        user_id = response.data[0].get('id')
        return jsonify({"id": user_id}), 201
    except Exception as e:
        return jsonify({"error": str(e)}), 500

以上です。
apiのリクエストで認証する際に毎回ユーザーidとpasswordを投げる設計になってしまってるのはよくないと思うので、アクセストークンにする等そこは改善ですね。

NCDCエンジニアブログ

Discussion

ログインするとコメントできます