uni-appを使用した微信ミニプログラムでのユーザー認証機能の実装

1. プロジェクト設定

manifest.json の設定

json

{
  "mp-weixin": {
    "appid": "あなたの小程序AppID",
    "setting": {
      "urlCheck": false
    },
    "usingComponents": true,
    "permission": {
      "scope.userLocation": {
        "desc": "近くのサービスを表示するために位置情報を取得します"
      }
    },
    "requiredPrivateInfos": [
      "getPhoneNumber"
    ]
  }
}

2. ユーザープロフィールの取得

ヘッダーとニックネームの取得 (ユーザー操作によるトリガー)

vue

<template>
  <view class="profile-container">
    <!-- ヘッダー画像の選択 -->
    <button class="header-btn" open-type="chooseAvatar" @chooseavatar="onSelectAvatar">
      <image class="header" :src="profile.avatar" mode="aspectFill"></image>
    </button>

    <!-- ニックネーム入力 -->
    <input 
      class="nickname-field" 
      type="text" 
      v-model="profile.nickName"
      placeholder="ニックネームを入力してください"
      @blur="onNickNameChange"
    />

    <view class="profile-status">
      <text>ヘッダー: {{ profile.avatar ? '設定済み' : '未設定' }}</text>
      <text>ニックネーム: {{ profile.nickName || '未設定' }}</text>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      profile: {
        avatar: '',
        nickName: ''
      }
    };
  },
  methods: {
    // アバター選択時の処理
    onSelectAvatar(e) {
      this.profile.avatar = e.detail.avatarUrl;
      this.uploadProfileImage(e.detail.avatarUrl);
    },

    // ニックネーム変更時の処理
    onNickNameChange(e) {
      this.profile.nickName = e.detail.value;
    },

    // サーバーへのプロフィール画像アップロード
    async uploadProfileImage(filePath) {
      try {
        const uploadTask = uni.uploadFile({
          url: 'あなたのサーバーアドレス',
          filePath: filePath,
          name: 'profileImage',
          formData: { type: 'header' },
          success: res => console.log('アップロード成功:', res),
          fail: err => console.error('アップロード失敗:', err)
        });
      } catch (error) {
        console.error('画像アップロードエラー:', error);
      }
    }
  }
};
</script>

<style>
.profile-container {
  padding: 30rpx;
}

.header-btn {
  width: 120rpx;
  height: 120rpx;
  border-radius: 50%;
  overflow: hidden;
  padding: 0;
  margin: 40rpx auto;
}

.header {
  width: 100%;
  height: 100%;
  border-radius: 50%;
}

.nickname-field {
  height: 80rpx;
  border: 1px solid #ddd;
  border-radius: 10rpx;
  padding: 0 20rpx;
  margin: 30rpx 0;
}

.profile-status {
  margin-top: 40rpx;
}

.profile-status text {
  display: block;
  margin: 10rpx 0;
  font-size: 28rpx;
}
</style>

3. ログイン機能の実装

vue

<template>
  <view class="auth-container">
    <!-- ログインボタン -->
    <button 
      v-if="!isLoggedIn" 
      type="primary" 
      open-type="getUserInfo" 
      @getuserinfo="handleUserInfo"
      @click="initiateLogin"
    >
      微信でログイン
    </button>

    <!-- ユーザー情報の表示 -->
    <view v-else class="user-profile">
      <image class="user-header" :src="user.avatar" mode="aspectFill"></image>
      <text class="user-name">{{ user.nickName }}</text>
      <button @click="signOut" class="logout">ログアウト</button>
    </view>

    <!-- ログインステータス -->
    <view class="auth-state">
      <text>ログイン状態: {{ isLoggedIn ? 'ログイン中' : '未ログイン' }}</text>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      isLoggedIn: false,
      user: {
        avatar: '',
        nickName: '',
        id: ''
      },
      authCode: ''
    };
  },
  onLoad() {
    this.verifyLoginStatus();
  },
  methods: {
    verifyLoginStatus() {
      const token = uni.getStorageSync('authToken');
      const userData = uni.getStorageSync('userDetails');
      if (token && userData) {
        this.isLoggedIn = true;
        this.user = userData;
      }
    },

    initiateLogin() {
      this.fetchAuthCode();
    },

    fetchAuthCode() {
      uni.login({
        provider: 'weixin',
        success: loginRes => {
          this.authCode = loginRes.code;
        },
        fail: err => {
          console.error('コード取得失敗:', err);
          uni.showToast({ title: 'ログインに失敗しました', icon: 'none' });
        }
      });
    },

    async handleUserInfo(e) {
      if (e.detail.userInfo) {
        this.user = {
          avatar: e.detail.userInfo.avatarUrl,
          nickName: e.detail.userInfo.nickName
        };

        try {
          const loginResponse = await this.performLogin({
            code: this.authCode,
            userInfo: this.user
          });

          if (loginResponse.success) {
            this.isLoggedIn = true;
            uni.setStorageSync('authToken', loginResponse.token);
            uni.setStorageSync('userDetails', this.user);
            uni.showToast({ title: 'ログイン成功', icon: 'success' });
          }
        } catch (error) {
          console.error('ログインエラー:', error);
          uni.showToast({ title: 'ログインに失敗しました', icon: 'none' });
        }
      } else {
        uni.showToast({ title: '承認が拒否されました', icon: 'none' });
      }
    },

    performLogin(loginData) {
      return new Promise((resolve, reject) => {
        uni.request({
          url: 'あなたのAPIエンドポイント',
          method: 'POST',
          data: loginData,
          success: res => {
            if (res.data.code === 200) {
              resolve({ success: true, token: res.data.data.token });
            } else {
              reject(res.data.message);
            }
          },
          fail: err => {
            reject(err);
          }
        });
      });
    },

    signOut() {
      this.isLoggedIn = false;
      this.user = { avatar: '', nickName: '', id: '' };
      uni.removeStorageSync('authToken');
      uni.removeStorageSync('userDetails');
      uni.showToast({ title: 'ログアウトしました', icon: 'success' });
    }
  }
};
</script>

4. 電話番号の取得

vue

<template>
  <view class="phone-section">
    <view class="phone-display">
      <text>電話番号: {{ phone || '未登録' }}</text>
    </view>

    <!-- 電話番号取得ボタン -->
    <button 
      v-if="!phone"
      open-type="getPhoneNumber" 
      @getphonenumber="fetchPhoneNumber"
      type="primary"
    >
      電話番号を登録
    </button>

    <button v-else @click="removePhone" class="unbind">
      電話番号を解除
    </button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      phone: ''
    };
  },
  onLoad() {
    this.checkPhoneBinding();
  },
  methods: {
    checkPhoneBinding() {
      const storedPhone = uni.getStorageSync('userPhone');
      if (storedPhone) {
        this.phone = storedPhone;
      }
    },

    async fetchPhoneNumber(e) {
      if (e.detail.errMsg === 'getPhoneNumber:ok') {
        try {
          const encryptedData = e.detail.encryptedData;
          const iv = e.detail.iv;

          const loginRes = await new Promise(resolve => {
            uni.login({ provider: 'weixin', success: resolve });
          });

          const decryptResult = await this.decryptPhone({
            code: loginRes.code,
            encryptedData,
            iv
          });

          if (decryptResult.success) {
            this.phone = decryptResult.phoneNumber;
            uni.setStorageSync('userPhone', this.phone);
            uni.showToast({ title: '登録完了', icon: 'success' });
          }
        } catch (error) {
          console.error('電話番号取得エラー:', error);
          uni.showToast({ title: '電話番号取得に失敗しました', icon: 'none' });
        }
      } else {
        uni.showToast({ title: '電話番号取得に失敗しました', icon: 'none' });
      }
    },

    decryptPhone(phoneData) {
      return new Promise((resolve, reject) => {
        uni.request({
          url: 'あなたのデコードAPIエンドポイント',
          method: 'POST',
          data: phoneData,
          success: res => {
            if (res.data.code === 200) {
              resolve({ success: true, phoneNumber: res.data.data.phoneNumber });
            } else {
              reject(res.data.message);
            }
          },
          fail: err => {
            reject(err);
          }
        });
      });
    },

    removePhone() {
      uni.showModal({
        title: '確認',
        content: '電話番号を解除しますか?',
        success: res => {
          if (res.confirm) {
            this.phone = '';
            uni.removeStorageSync('userPhone');
            uni.showToast({ title: '解除完了', icon: 'success' });
          }
        }
      });
    }
  }
};
</script>

タグ: uni-app WeChatMiniProgram UserAuthentication

6月30日 17:12 投稿