1. Kakao Developers 설정
Kakao Developers
카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.
developers.kakao.com
카카오 디벨로퍼에서 애플리케이션을 생성해야한다.
API를 추가할 애플리케이션을 생성한다.
생성하면 앱 키 에서 각종 키들을 사용할 수 있게 된다.
구글 OAuth2.0 설정과 마찬가지로 카카오도 Redirection URI를 설정해주어야 한다. 로그인 성공 후 해당 URI로 리다이렉션 된다.
테스트앱을 생성하여 테스트를 할 수 있으나 사업자가 필요하므로, 개인적인 테스트를 위한 사람들은 그냥 넘어가도록 하자.
사업자 등록을 하지 않게 된다면 설정할 수 있는 권한이 매우 적다. 우선 기본적인 닉네임을 가지고 테스트하도록 한다.
설정이 완료되었으면 활성화 설정을 ON으로 전환한다.
2. 패키지 인스톨
npm install passport passport-kakao
3. .env 등록
KAKAO_CLIENT_ID=[카카오에서 받은 REST API]
KAKAO_CALLBACK_URL=http://localhost:3000/auth/kakao/callback
//kakao.config.ts
import { registerAs } from '@nestjs/config';
export default registerAs('kakao', () => ({
clientId: process.env.KAKAO_CLIENT_ID, // 카카오 앱의 REST API 키
callbackUrl: process.env.KAKAO_CALLBACK_URL,
}));
4. Config를 사용할 수 있도록 모듈 등록
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtModule } from '@nestjs/jwt';
import { UsersModule } from 'src/users/users.module';
import { GoogleStrategy } from './strategies/google.strategy';
import { ConfigModule } from '@nestjs/config';
import googleConfig from 'src/configs/google.config';
import kakaoConfig from 'src/configs/kakao.config';
import { KakaoStrategy } from './strategies/kakao.strategy';
@Module({
imports: [
ConfigModule.forFeature(googleConfig),
ConfigModule.forFeature(kakaoConfig),
JwtModule.register({}),
UsersModule,
],
exports: [AuthService],
controllers: [AuthController],
providers: [AuthService, GoogleStrategy, KakaoStrategy],
})
export class AuthModule {}
마찬가지로 auth에서 사용하게 될 것이므로, auth.module.ts 에 위와 같이 kakaoConfig를 추가한다.
5. UsersModel 에 kakao 컬럼 추가
@Column({
unique: true,
nullable: true,
})
@IsString()
kakao: string;
UsersModel에 kakao 컬럼을 추가하여준다. nullable이 true여야 기존의 DB가 있어도 추가될 수 있게된다.
6. kakao로 로그인한 사용자 찾거나 생성하는 로직 구현
//users.service.ts
async findOrCreateByKakao({
email,
nickname,
kakaoId,
}: {
email: string;
nickname: string;
kakaoId: string;
}): Promise<UsersModel> {
// 먼저, kako 필드가 kakaoId와 매칭되는 사용자가 있는지 확인
let user = await this.usersRepository.findOne({ where: { kakao: kakaoId } });
// 만약 사용자가 없다면, email로도 찾을 수 있다면 두 가지를 병합할 수도 있음
if (!user && email) {
user = await this.usersRepository.findOne({ where: { email } });
}
//todo: 카카오 임시로 이메일 설정
if (!email) {
email = 'stack@test.com';
}
// 사용자가 존재하지 않는다면 신규 생성
if (!user) {
user = await this.createUser({
email,
nickname: nickname,
password: '', // 카카오 로그인은 패스워드가 필요 없으므로 빈 문자열 지정
kakao: kakaoId, // 카카오 고유 식별자 저장
});
} else if (!user.kakao) {
// 기존에 email로 가입된 사용자의 경우, 구글 연동이 안 되어 있다면 google 필드 업데이트
user.kakao = kakaoId;
await this.usersRepository.save(user);
}
return user;
}
사업자가 아니기 때문에 카카오로부터 받아올 수 있는 것은 '닉네임' 뿐이므로, email을 임시로 설정해주었다.
email은 UsersModel에서 unique가 true이므로 nullable이 false이다. 그렇다는 소리는 유저를 생성할 때는 반드시 email이 있어야한다는 뜻이 된다.
그렇기 때문에 email을 임시로 작업해서 넣어주었다.
7. KakaoStrategy 구현
import { Injectable, Inject } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, Profile, VerifyCallback } from 'passport-kakao';
import { ConfigType } from '@nestjs/config';
import kakaoConfig from 'src/configs/kakao.config';
import { UsersService } from 'src/users/users.service';
@Injectable()
export class KakaoStrategy extends PassportStrategy(Strategy, 'kakao') {
constructor(
@Inject(kakaoConfig.KEY)
private readonly config: ConfigType<typeof kakaoConfig>,
private readonly usersService: UsersService,
) {
super({
clientID: config.clientId,
callbackURL: config.callbackUrl,
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifyCallback,
): Promise<any> {
// profile에서 필요한 정보를 추출합니다.
// passport-kakao는 profile 객체 내 _json 속성에 추가 정보를 포함합니다.
const { id, username, _json } = profile;
const email =
_json?.kakao_account && _json.kakao_account.email
? _json.kakao_account.email
: null;
/*
UsersService에 kakaOAuth 전용 메소드 (예: findOrCreateByKakao())를 만들어서
기존 사용자와의 중복 검사 및 신규 생성 로직을 처리할 수 있습니다.
예)
async findOrCreateByKakao({ kakaoId, email, nickname }: { kakaoId: string; email?: string; nickname: string; }): Promise<UsersModel>;
*/
const user = await this.usersService.findOrCreateByKakao({
email,
nickname: username,
kakaoId: id,
});
done(null, user);
}
}
카카오 로그인이 실행될 때 처리되는 KakaoStrategy이다. Passport에 의해 구동된다.
8. KakaoAuthGuard 구현
import { Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Injectable()
export class KakaoAuthGuard extends AuthGuard("kakao") {
async canActivate(context: any): Promise<boolean> {
const result = (await super.canActivate(context)) as boolean;
return result;
}
}
9. 컨트롤러 등록
@Get('login/kakao')
@IsPublic(IsPublicEnum.IS_PUBLIC)
@UseGuards(KakaoAuthGuard)
kakaoAuth() {
// 이 엔드포인트는 KakaoAuthGuard가 리다이렉션 처리합니다.
console.log('GET kakao/login');
}
@Get('kakao/callback')
@IsPublic(IsPublicEnum.IS_PUBLIC)
@UseGuards(KakaoAuthGuard)
kakaoAuthRedirect(@User() user: UsersModel) {
return this.authService.loginUser(user);
}
auth.controller.ts 에 라우터들을 구현한다.
10. 실행 결과
http://localhost:3000/auth/login/kakao
위의 경로를 웹페이지에 입력하면 위와 같이 카카오로그인 화면이 뜬다.
http://localhost:3000/auth/kakao/callback
이후 계정을 선택하면 위 경로가 호출되며 토큰이 발급되는 것을 볼 수 있다.
마찬가지로 DB에도 등록이 되며 kakao 컬럼에 정상적으로 값이 입력되어있다.
11. 카카오계정과 함께 로그아웃
11.1. 카카오 로그아웃 리다이렉트 URI 추가
사용자가 다른 아이디로 로그인하고 싶을 경우거나, 강제로 로그아웃시켜버릴 경우가 있을 수 있다.
카카오 로그인 - 고급 항목에 Logout Redirect URI 등록을 선택한다.
컨트롤러에서 kakao/logout/callback 엔드포인트를 추가할 예정이므로 위와 같이 등록하였다.
11.2. 라우터 추가
GET https://kauth.kakao.com/oauth/logout?client_id={REST_API_KEY}&logout_redirect_uri={LOGOUT_REDIRECT_URI}
카카오는 REST API Key와 방금 등록한 Logout Redirect URI를 쿼리파라미터로 추가하여 GET 요청하도록 하고 있다.
@Get('logout/kakao')
@IsPublic(IsPublicEnum.IS_PUBLIC)
kakaoAuthLogout(@Res() res: Response) {
const restApiKey = this.config.clientId;
// 로그아웃 후 사용자에게 리다이렉트할 URL 설정 (예: 홈 페이지 또는 로그인 페이지)
const logoutRedirectUri = this.config.logoutCallbackUrl as string;
const logoutUrl = `https://kauth.kakao.com/oauth/logout?client_id=${restApiKey}&logout_redirect_uri=${encodeURIComponent(logoutRedirectUri)}`;
return res.redirect(logoutUrl);
}
@Get('kakao/logout/callback')
@IsPublic(IsPublicEnum.IS_PUBLIC)
kakaoAuthLogoutRedirect(@Res() res: Response) {
res.send('<html><body><h1>카카오계정이 로그아웃 되었습니다.</h1></body></html>');
}
auth 컨트롤러에서 로그아웃 엔드포인트와 로그아웃 시 콜백될 엔드포인트를 구현했다.
브라우저에선 Postman과 같이 AccessToken을 유지하도록 테스트할 수 없어서 임시로 @IsPublic 데코레이터를 추가하였다.
11.3. 실행결과
http://localhost:3000/auth/logout/kakao 를 브라우저에 입력하면 위와 같이 로그아웃 화면이 뜬다.
Callback 도 잘오는 것을 확인할 수 있다.
이후 다시 http://localhost:3000/auth/login/kakao 를 입력하면 기존에는 바로 로그인이 되어 accessToken과 refreshToken 발급화면이 보여졌지만, 이젠 처음부터 계정을 선택하도록 되었으며 로그아웃이 잘 적용된 것을 볼 수 있다.
'NestJS' 카테고리의 다른 글
[NestJS] Redis 적용하기 - Rate Limiting (0) | 2025.04.21 |
---|---|
[NestJS] Redis 적용하기 (캐싱) (0) | 2025.04.17 |
[NestJS] Google Oauth2.0 Passport (0) | 2025.04.12 |
[NestJS] Swagger 사용법 (1) | 2025.04.07 |
[NestJS] process.env 사용 및 분기처리 (0) | 2025.04.05 |