이번에는 refreshToken을 발행하면서 cookie에 refreshToken이 잘 들어가는지 확인해 보고 토큰이 만료되었을 때의 에러를 확인해 보자
auth.resolver.ts 파일을 다음과 같이 수정해준다.
// auth.resolver.ts
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql';
import { IContext } from 'src/commons/interfaces/context';
import { AuthService } from './auth.service';
@Resolver()
export class AuthResolver {
constructor(
private readonly authService: AuthService, //
) {}
@Mutation(() => String)
async login(
@Args('email') email: string, //
@Args('password') password: string,
@Context() context: IContext, // 추가된 부분 - context
): Promise<string> {
return this.authService.login({ email, password, context });
}
}
@Context() : Request와 Response, header 등에 대한 정보들이 context에 존재한다.
따라서 데코레이터를 통해 해당 context 정보를 가지고 올 수 있도록 설정해줘야 한다.
그리고 app.module 파일을 아래와 같이 수정해 주세요.
// app.module.ts
@Module({
imports: [
AuthModule,
BoardsModule,
ProductsModule,
ProductsCategoriesModule,
UsersModule,
ConfigModule.forRoot(),
// 추가해야할 부분
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'src/commons/graphql/schema.gql',
context: ({ req, res }) => ({ req, res }), // req는 기본적으로 들어오지만, res는 이걸 작성해야만 들어옴
}),
TypeOrmModule.forRoot({
type: process.env.DATABASE_TYPE as 'mysql',
host: process.env.DATABASE_HOST,
port: Number(process.env.DATABASE_PORT),
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_DATABASE,
entities: [__dirname + '/apis/**/*.entity.*'],
synchronize: true,
logging: true,
}),
],
})
export class AppModule {}
위와같은 설정을 통해 graphql로 들어온 req와 res를 API 들에서 사용할 수 있게끔 설정할 수 있다.
auth.service.ts 파일에 setRefreshToken 이라는 비즈니스 로직을 추가해준다.
// auth.service.ts
@Injectable()
export class AuthService {
constructor(
private readonly jwtService: JwtService,
private readonly usersService: UsersService, //
) {}
async login({
email,
password,
context,
}: IAuthServiceLogin): Promise<string> {
// 1. 이메일이 일치하는 유저를 DB에서 찾기
const user = await this.usersService.findOneByEmail({ email });
// 2. 일치하는 유저가 없으면?! 에러 던지기!!!
if (!user) throw new UnprocessableEntityException('이메일이 없습니다.');
// 3. 일치하는 유저가 있지만, 비밀번호가 틀렸다면?!
const isAuth = await bcrypt.compare(password, user.password);
if (!isAuth) throw new UnprocessableEntityException('암호가 틀렸습니다.');
// 4. refreshToken(=JWT)을 만들어서 브라우저 쿠키에 저장해서 보내주기
this.setRefreshToken({ user, context });
// 5. 일치하는 유저도 있고, 비밀번호도 맞았다면?!
// => accessToken(=JWT)을 만들어서 브라우저에 전달하기
return this.getAccessToken({ user });
}
// 추가된 부분
setRefreshToken({ user, context }: IAuthServiceSetRefreshToken): void {
const refreshToken = this.jwtService.sign(
{ sub: user.id },
{ secret: '나의리프레시비밀번호', expiresIn: '2w' },
);
// 개발환경
context.res.setHeader(
'set-Cookie',
`refreshToken=${refreshToken}; path=/;`,
);
// 배포환경
// context.res.setHeader('set-Cookie', `refreshToken=${refreshToken}; path=/; domain=.mybacksite.com; SameSite=None; Secure; httpOnly`);
// context.res.setHeader('Access-Control-Allow-Origin', 'https://myfrontsite.com');
}
getAccessToken({ user }: IAuthServiceGetAccessToken): string {
return this.jwtService.sign(
{ sub: user.id },
{ secret: '나의비밀번호', expiresIn: '1h' },
);
}
}
login로직 내에서 setRefreshToken 로직을 실행해 context 안에 존재하는 res 객체의 cookie에 refreshToken 토큰을 넣어준다.refreshToken은 return을 사용해서 프론트로 보내주는 것이 아니라setRefreshToken 로직res.setHeader())의 cookie 부분('Set-Cookie’)에 refreshToken을 추가해 준다.void 로 설정한다.IAuthServiceSetRefreshToken을 import한다.auth → interfaces → auth-service.interface.ts 파일에 IAuthServiceSetRefreshToken을 아래와 같이 만들어서 타입을 정의해준다.// auth-service.interface.ts
import { User } from 'src/apis/users/entities/user.entity';
import { IContext } from 'src/commons/interfaces/context';
// 추가된 부분
export interface IAuthServiceSetRefreshToken {
user: User;
context: IContext;
}
- IContext도 express에서 타입을 쓰도록 다음과 같이 수정해준다.
// context.ts
import { Request, Response } from 'express';
export interface IAuthUser {
user?: {
id: string;
};
}
export interface IContext {
req: Request & IAuthUser;
res: Response;
}
서버를 실행하고 http://localhost:3000/graphql 에 접속해서 플레이그라운드에서 api 요청을 해보면 아래와 같이 나온다.

먼저 login을 진행해보면

브라우저상에서 쿠키 안에 refreshToken이 쿠키에 넣어져온걸 확인할 수 있다.
현재의 쿠키는 저장될 예정인 쿠키들을 보여준다.
실제 저장된 쿠키는 Application 탭에서 확인 가능하다.

cookie에서 refreshToken 확인이 안된다면?
🚨 만약 refreshToken이 들어오지 않는다면 플레이그라운드 설정을 다음과 같이 설정해준다.
graphql playground의 setting 값을 적용해야 header의 cookies 값을 확인할 수 있다.
( 플레이그라운드 설정은 오른편 상단 설정⚙️을 클릭하면 설정 화면으로 들어갈 수 있으며, 설정 후 반드시 저장(SAVE SETTINGS) 해야한다. )