前回記事の続き、今回はバックエンドとアプリの続きを実装
PR
backend
アプリ
実装
まずはbackendを実装
今回はNestJSのGuardの機能を使いFirebase認証を作成
コードは以下のような感じ
■ 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は、以下のように設定して参照することができる
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を使用して実装
コードは以下のような感じで実装
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認証までの実装が完了。基本的な開発は終わったので次回からアプリ自体の作り込みを行う予定