npm install firebase
npm install firebase-admin
src/main.ts の bootstrap() に以下を追加する
firebaseコンソールからサービスアカウントのjsonファイルをダウンロードしておきます(コンソール→プロジェクトの設定→サービスアカウント→新しい秘密鍵を生成)
import * as admin from 'firebase-admin';
import * as serviceAccount from '/PATH/TO/YOUR/SERVICE/ACCOUNT.json';
async function bootstrap() {
// firebase-admin
const params = {
type: serviceAccount.type,
projectId: serviceAccount.project_id,
privateKeyId: serviceAccount.private_key_id,
privateKey: serviceAccount.private_key,
clientEmail: serviceAccount.client_email,
clientId: serviceAccount.client_id,
authUri: serviceAccount.auth_uri,
tokenUri: serviceAccount.token_uri,
authProviderX509CertUrl: serviceAccount.auth_provider_x509_cert_url,
clientC509CertUrl: serviceAccount.client_x509_cert_url,
};
admin.initializeApp({
credential: admin.credential.cert(params),
});
Nest.jsにおけるGuardは実際には次の条件を全て満たすようなものです:
・クラスである
・@Injectable()デコレーターでアノテーションされている
・CanActivateというインターフェースをimplementsしている
・canActivateという、ExecutionContext型を引数にとり、同期または非同期でboolean値(trueまたはfalse)を返すメソッドを実装している
src/auth/guard/auth.guard.ts
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from "@nestjs/common";
import { AuthService } from "../auth.service";
import { GqlExecutionContext } from "@nestjs/graphql";
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly authService: AuthService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const requestHeaders = ctx.getContext().req.headers;
if (!requestHeaders) {
throw new Error("ヘッダが正しく設定されていません。");
}
const idToken: string = requestHeaders.authorization.replace("Bearer ", "");
try {
const user = await this.authService.validateUser(idToken);
ctx.getContext().req["user"] = user;
return user !== undefined;
} catch (error) {
throw new UnauthorizedException("認証情報が正しくありません。");
}
}
}
「CanActivate」 は 結果の返却は、Booleanのため、情報の再利用はできません。
「AuthGuard」を 使用すると結果の返却は Object | Boolean となるため 情報の再利用が可能となります
src/users/users.module.ts
import { AuthService } from '../auth/auth.service';
// AuthService を追加します
@Module({
providers: [UsersResolver, UsersService, AuthService],
})
@Query(() => User, { name: 'user' })
findOne(@Args('id', { type: () => Int }) id: number) {
return this.usersService.findOne(id);
}
↓
メソッドのレベルで使用するために、次のように @UseGuards() デコレータを使用します
import { UseGuards } from '@nestjs/common';
@UseGuards(AuthGuard)
@Query(() => User, { name: 'user' })
findOne(@Args('id', { type: () => Int }) id: number) {
return this.usersService.findOne(id);
}
以上です。
https://docs.nestjs.com/recipes/passport
@nestjs/passport の インストール
npm install --save @nestjs/passport passport passport-jwt passport-http-bearer
以下の「ストラテジ」「モジュール」「ガード」を作成して @UseGuards(FirebaseAuthGuard) という記述でガードを使用できるようにします。
src/auth/firebase.strategy.ts
import {
HttpException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-http-bearer';
import { DecodedIdToken } from 'firebase-admin/lib/auth';
import * as admin from 'firebase-admin';
@Injectable()
export class FirebaseStrategy extends PassportStrategy(Strategy, 'firebase') {
constructor() {
super();
}
async validate(idToken: string): Promise<DecodedIdToken> {
if (!idToken) {
throw new UnauthorizedException('認証が必要です。');
}
try {
return await admin.auth().verifyIdToken(idToken);
} catch (error) {
throw new HttpException('Forbidden', error);
}
}
}
src/auth/firebase/firebase.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { FirebaseStrategy } from './firebase.strategy';
@Module({
imports: [PassportModule.register({ defaultStrategy: 'firebase' })],
providers: [FirebaseStrategy],
exports: [PassportModule],
})
export class FirebaseModule {}
src/auth/firebase/firebaseAuth.guard.ts
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';
@Injectable()
export class FirebaseAuthGuard extends AuthGuard('firebase') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
使い方は同じです。 ガードを加えます。
@Query(() => User, { name: 'user' })
↓
@UseGuards(FirebaseAuthGuard)
@Query(() => User, { name: 'user' })
参考 : https://hi1280.hatenablog.com/
デコレーターを作成し、そこから取り出します。
src/auth/firebase/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
export type CurrentFirebaseUserData = {
uid: string;
name: string;
email: string;
};
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
const requestUser = ctx.getContext().req['user'];
const currentUser: CurrentFirebaseUserData = {
uid: requestUser.uid,
name: requestUser.name,
email: requestUser.email,
};
return currentUser;
},
);
使い方
@UseGuards(FirebaseAuthGuard)
@Query(() => User, { name: 'profile' })
profile(@CurrentUser() user: CurrentFirebaseUserData) {
return this.usersService.findOneFromEmail(user.email);
}
このようにして profile メソッドに user を渡すことができます。
graphql クエリ例
{
profile{
id,name,email,authProvider,authId,createdAt,updatedAt
}