wheatandcatの開発ブログ

React Nativeで開発しているペペロミア & memoirの技術系記事を投稿してます

Flutter + NestJsでFirebase Authenticationを実装②

前回記事の続き、今回はバックエンドとアプリの続きを実装

PR

backend

github.com

アプリ

github.com

実装

まずはbackendを実装
今回はNestJSのGuardの機能を使いFirebase認証を作成

docs.nestjs.com

コードは以下のような感じ

src/common/guards/auth/auth.guard.ts

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
import { GqlExecutionContext } from '@nestjs/graphql'
import { PrismaService } from '@src/modules/prisma/prisma.service'
import admin from 'firebase-admin'
import { type User } from '@prisma/client'

export type Auth = {
  uid: string
  userId: number
  user: User
}

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private prisma: PrismaService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const ctx = GqlExecutionContext.create(context)
    const request = ctx.getContext().req

    const token = request.headers['authorization']
    const result = await admin.auth().verifyIdToken(token)

    const user = await this.prisma.user.findFirst({
      where: {
        uid: result.uid,
      },
    })

    if (!user) {
      throw new Error('User not found')
    }

    request.auth = {
      uid: result.uid,
      userId: user.id,
      user: user,
    } as Auth

    return true
  }
}

コードの中でHeaderに設定しているAuthorizationを取得して、Firebaseで認証を行い、ユーザー情報を取得
取得したuidでprismaからデータを取得してrequest.authに設定

作成したGuardは、以下のように設定して参照することができる

src/resolver/user.ts

import { AuthGuard } from '@src/common/guards/auth/auth.guard'
import admin from 'firebase-admin'
import { Prisma } from '@prisma/client'

@Resolver('')
export class UserResolver {
  constructor(private prisma: PrismaService) {}

  @Query('me')
  @UseGuards(AuthGuard)
  async me(@Context() context): Promise<QueryType['me']> {
    const user = context.req.auth

    const r = await this.prisma.user.findFirst({
      where: {
        uid: user.uid,
      },
    })
    return format(r)
  }

これでbackendでFrontendから送ったFirebaseのtoken IDを認証してFirebaseのユーザーデータを取得する処理が実装できた
次は、Flutter側の開発でAPIの接続 & 認証情報のステート管理を実装

認証情報はグローバルステートで定義したいのでRiverpodを使用して実装

pub.dev

コードは以下のような感じで実装

lib/providers/user.dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:sampleflutter/providers/graphql.dart';
import 'package:sampleflutter/graphql/me.gql.dart';

class UserData {
  late final String id;
  late final String uid;

  UserData({
    required this.id,
    required this.uid,
  });

  void update(UserData newUser) {
    id = newUser.id;
    uid = newUser.uid;
  }
}

final firebaseAuthProvider =
    Provider<FirebaseAuth>((ref) => FirebaseAuth.instance);

final authStateChangesProvider = StreamProvider<User?>((ref) {
  return ref.read(firebaseAuthProvider).authStateChanges();
});

final userDataProvider = FutureProvider.autoDispose<UserData?>((ref) async {
  final user = ref.watch(authStateChangesProvider).asData?.value;
  if (user != null) {
    final client = ref.read(graphqlClientProvider);

    final result = await client.query<Query$Me>(
      QueryOptions(
        document: documentNodeQueryMe,
      ),
    );
    if (result.hasException) {
      return null;
    }

    final userData = result.data!['me'];
    return UserData(
      id: userData['id'],
      uid: userData['uid'],
    );
  }
  return null;
});

firebaseAuthProviderを宣言して、authStateChangesで認証状態を監視。ユーザーが取得できたらUserDataを更新するように実装
認証したデータを取得する場合は以下のようにする

lib/app/categories/page.dart

import 'package:sampleflutter/providers/user.dart';
import 'package:sampleflutter/app/items/new/page.dart';
import 'package:sampleflutter/features/category/hooks/useItems.dart';

class MyHomePage extends HookConsumerWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {

    final userDataAsyncValue = ref.watch(userDataProvider);
    debugPrint('userDataAsyncValue: ${userDataAsyncValue.asData?.value?.id}') // ← ここで認証したユーザーデータを参照できる

こんな感じでFirebase認証までの実装が完了。基本的な開発は終わったので次回からアプリ自体の作り込みを行う予定