@Controller('auth')
export class AuthController {
constructor(private readonly kakaoService: KakaoService) {}
@Get("kakaoLogin")
@Header('Content-Type', 'text/html')
getKakaoLoginPage():string {
return `
<div>
<h1>카카오 로그인</h1>
<form action="/auth/kakaoLoginLogic" method="GET">
<input type="submit" value="카카오 로그인" />
</form>
<form action="/auth/kakaoLogout" method="GET">
<input type="submit" value="카카오 로그아웃" />
</form>
`;
}
}
@Get('/kakaoLoginLogic')
@Header('Content-Type', 'text/html')
kakaoLoginLogic(@Res() res): void {
const _hostName = 'https://kauth.kakao.com';
const _restApiKey = '450af5f4744fcb0bc3d057ba542c7d6a';
// 카카오 로그인 redirectURI 등록
const _redirectUrl = 'http://127.0.0.1:3000/auth/kakaoLoginLogicRedirect';
const url = `${_hostName}/oauth/authorize?client_id=${_restApiKey}&redirect_uri=${_redirectUrl}&response_type=code`;
return res.redirect(url);
}
//server/src/auth/auth.controller.ts
@Get('/kakaoLoginLogicRedirect')
@Header('Content-Type', 'text/html')
kakaoLoginLogicRedirect(@Query() qs, @Res() res):void {
console.log(qs.code); //Query()에서 code를 가져온다
//server/src/auth/auth.controller.ts
//토큰을 발급받는 과정
@Get('/kakaoLoginLogicRedirect')
@Header('Content-Type', 'text/html')
kakaoLoginLogicRedirect(@Query() qs, @Res() res):void {
console.log(qs.code);
const _restApiKey = '450af5f4744fcb0bc3d057ba542c7d6a';
const _redirect_uri = 'http://127.0.0.1:3000/auth/kakaoLoginLogicRedirect';
//code, restApiKey, _redirect_uri를 다시 카카오 api에게 POST요청을 보내준다
const _hostName = `https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=${_restApiKey}&redirect_uri=${_redirect_uri}&code=${qs.code}`;
const _headers = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
},
};
//POST 요청을 위한 kakaoService
this.kakaoService
.login(_hostName, _headers)
.then((e) => {
// console.log(e);
console.log(`TOKEN : ${e.data['access_token']}`);
this.kakaoService.setToken(e.data['access_token']);
return res.send(`
<div>
<h2>축하합니다!</h2>
<p>카카오 로그인 성공하였습니다!</p>
<a href="/auth/kakaoLogin">메인으로</a>
</div>
`);
})
.catch((err)=> {
console.log(err);
return res.send('error');
});
//server/src/auth/kakao.service.ts
import { Injectable } from "@nestjs/common";
import { HttpService } from "@nestjs/axios";
import { lastValueFrom } from "rxjs";
@Injectable()
export class KakaoService {
check: boolean;
accessToken: string;
private http: HttpService;
constructor() {
this.check = false;
this.http = new HttpService();
this.accessToken = '';
}
loginCheck(): void {
this.check = !this.check;
return ;
}
async login(url: string, headers: any): Promise<any> {
return await lastValueFrom(
this.http.post(url, '', { headers })
);
}
setToken(token: string): boolean {
this.accessToken = token;
return true;
}
}
*기존에 사용했었던 toPromise() method가 deprectaed 되었다는 것이다.
import { lastValueFrom } from "rxjs";
...
async login(url: string, headers: any): Promise<any> {
return await lastValueFrom(
this.http.post(url, '', { headers })
);
}
...
//server/src/auth/auth.controller.ts
@Get('/kakaoLoginLogicRedirect')
@Header('Content-Type', 'text/html')
kakaoLoginLogicRedirect(@Query() qs, @Res() res):void {
...
this.kakaoService
.login(_hostName, _headers)
.then((e) => {
// console.log(e);
console.log(`TOKEN : ${e.data['access_token']}`);
this.kakaoService.setToken(e.data['access_token']);
this.kakaoUserInfo(e.data);
...
}
@Get('/kakaoLogin')
@Header('Content-Type', 'text/html')
async kakaoUserInfo(@Res() res) {
//GET요청을 보내기 위해 필요한 정보들
const _url = "https://kapi.kakao.com/v2/user/me";
// console.log(res);
const _headers = {
headers: {
'Authorization': `Bearer ${res.access_token}`,
},
}
// console.log(`토큰: ${res.access_token}`)
this.kakaoService
.showUserInfo(_url, _headers.headers)
.then((e)=> {
console.log(e);
})
.catch((err)=> {
console.log(err);
return res.send('error');
})
//server/src/auth/kakao.service.ts
...
async showUserInfo(url: string, headers: any): Promise<any> {
// console.log(`헤더: ${JSON.stringify(headers.headers)}`)
return await lastValueFrom(
this.http.get(url, { headers })
);
}
...
성공적으로, user의 data를 얻어 올 수 있다.
로그아웃 하기 전, kakao Developer 에서 로그아웃 시 Redirect URL를 설정해둬야 한다.
앞서 access_token을 setToken() 메서드를 통해class에 저장해두었기 때문에, 이를 활용한다.
로그아웃의 종류에는 토큰 만료와 로그 삭제가 있다.
- 토큰 만료: 일반적으로 생각하는 로그아웃
- 로그 삭제: *탈퇴 or 다른 카카오 아이디로 로그인을 유도하는 경우의 로그아웃*
//server/src/auth/auth.controller.ts
// 로그아웃 (일반적인 로그아웃, 토큰 만료)
@Get('/kakaoLogout')
@Header('Content-Type', 'text/html')
kakaoLogout(@Res() res): void {
this.kakaoService
.logout()
.then((e)=>{
console.log(e);
return res.send(`
<div>
<h2>로그아웃 완료(토큰만료)</h2>
<a href="/auth/kakaoLogin">메인 화면으로</a>
</div>
`);
})
.catch((err)=>{
console.log(err);
return res.send('logout error');
})
}
//server/src/auth/kakao.service.ts
async logout(): Promise<any> {
const _url = 'https://kapi.kakao.com/v1/user/logout';
const _headers = {
Authorization: `Bearer ${this.accessToken}`,
};
console.log(this.accessToken);
//console.log(JSON.stringify(_headers));
return await lastValueFrom(
this.http.post(_url, '', { headers: _headers })
);
}
//로그 아웃 (탈퇴 or 다른 카카오 아이디로 로그인을 유도하는 경우, 로그 삭제)
@Get('/kakaoLogout')
@Header('Content-Type', 'text/html')
kakaoLogout(@Res() res): void {
this.kakaoService
.deleteLog()
.then((e)=>{
console.log(e);
return res.send(`
<div>
<h2>로그아웃 완료(로그삭제)</h2>
<a href="/auth/kakaoLogin">메인 화면으로</a>
</div>
`);
})
.catch((err)=>{
console.log(err);
return res.send('logout error');
})
}
//server/src/auth/kakao.service.ts
async deleteLog(): Promise<any> {
const _url = "https://kapi.kakao.com/v1/user/unlink";
const _headers = {
Authorization: `Bearer ${this.accessToken}`,
}
return await lastValueFrom(
this.http.post(_url, '', { headers: _headers})
)
}
참고자료: https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
[https://www.youtube.com/watch?v=p5LASg_uUb0&t=261s](https://www.youtube.com/watch?v=p5LASg_uUb0&t=261s)
https://devtalk.kakao.com/t/access-token-should-not-be-null-or-empty/110977