-
Notifications
You must be signed in to change notification settings - Fork 2
[준섭] 1125(토) 개발 기록 ‐ OAuth 코드 통합 및 재사용
송준섭 edited this page Nov 25, 2023
·
1 revision
원래 나는 깃헙 로그인 개발 기록에서의 방법처럼 소셜 로그인 하나 하나 API를 따로 만드려고 하였다.
그러나 OAuth에서 AccessToken을 받아오는 방법이나, 유저 정보를 받아오는 방법이 서비스 별로 URL 정도만 다르고 비슷하기 때문에 그냥 통합해서 재사용 할 수 있지 않을까? 라는 생각이 들었다.
그래서 이 코드를 리팩토링하여 최대한 재사용하고자 하였다.
@Get('github/signin')
signInWithGithub(@Res({ passthrough: true }) res: Response) {
res.redirect(
`https://github.com/login/oauth/authorize?client_id=${process.env.OAUTH_GITHUB_CLIENT_ID}&scope=read:user%20user:email`,
);
}
@Get('naver/signin')
signInWithGithub(@Res({ passthrough: true }) res: Response) {
res.redirect(
`https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${process.env.OAUTH_NAVER_CLIENT_ID}&redirect_uri=${process.env.OAUTH_NAVER_REDIRECT_URI}&state=STATE_STRING`,
);
}
@Get('github/callback')
async oauthGithubCallback(
@Query('code') authorizedCode: string,
@Res({ passthrough: true }) res: Response,
) {
const { username, accessToken, refreshToken } =
await this.authService.oauthGithubCallback(authorizedCode);
if (username) {
res.cookie('GitHubUsername', username, {
path: '/',
httpOnly: true,
});
return { username };
}
res.cookie(JwtEnum.ACCESS_TOKEN_COOKIE_NAME, accessToken, {
path: '/',
httpOnly: true,
});
res.cookie(JwtEnum.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
path: '/',
httpOnly: true,
});
return { accessToken, refreshToken };
}
@Get('naver/callback')
async oauthGithubCallback(
@Query('code') authorizedCode: string,
@Res({ passthrough: true }) res: Response,
) {
const { username, accessToken, refreshToken } =
await this.authService.oauthNaverCallback(authorizedCode);
if (username) {
res.cookie('NaverUsername', username, {
path: '/',
httpOnly: true,
});
return { username };
}
res.cookie(JwtEnum.ACCESS_TOKEN_COOKIE_NAME, accessToken, {
path: '/',
httpOnly: true,
});
res.cookie(JwtEnum.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
path: '/',
httpOnly: true,
});
return { accessToken, refreshToken };
}
@Post('github/signup')
async signUpWithGithub(
@Body('nickname') nickname: string,
@Req() req,
@Res({ passthrough: true }) res: Response,
) {
let gitHubUsername;
try {
gitHubUsername = req.cookies.GitHubUsername;
} catch (e) {
throw new UnauthorizedException('잘못된 접근입니다.');
}
const savedUser = await this.authService.signUpWithGithub(
nickname,
gitHubUsername,
);
res.clearCookie('GitHubUsername', {
path: '/',
httpOnly: true,
});
return savedUser;
}
@Post('naver/signup')
async signUpWithNaver(
@Body('nickname') nickname: string,
@Req() req,
@Res({ passthrough: true }) res: Response,
) {
let naverUsername;
try {
naverUsername = req.cookies.NaverUsername;
} catch (e) {
throw new UnauthorizedException('잘못된 접근입니다.');
}
const savedUser = await this.authService.signUpWithNaver(
nickname,
gitHubUsername,
);
res.clearCookie('NaverUsername', {
path: '/',
httpOnly: true,
});
return savedUser;
}
원래 코드는 위처럼 각 리소스 서버마다 API를 따로 구현하려고 구상했었다.
그러나 어차피 코드도 다 비슷한데 이렇게 구분할 이유가 있나? 라는 생각이 들었다.
그래서 리팩토링을 진행하였다.
@Get(':service/signin')
signInWithOAuth(
@Param('service') service: string,
@Res({ passthrough: true }) res: Response,
) {
let redirectUrl: string;
switch (service) {
case 'github':
redirectUrl = `https://github.com/login/oauth/authorize?client_id=${process.env.OAUTH_GITHUB_CLIENT_ID}&scope=read:user%20user:email`;
break;
case 'naver':
redirectUrl = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${process.env.OAUTH_NAVER_CLIENT_ID}&redirect_uri=${process.env.OAUTH_NAVER_REDIRECT_URI}&state=STATE_STRING`;
break;
case 'google':
redirectUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.OAUTH_GOOGLE_CLIENT_ID}&redirect_uri=${process.env.OAUTH_GOOGLE_REDIRECT_URI}&response_type=code&scope=email%20profile`;
break;
default:
throw new NotFoundException('존재하지 않는 서비스입니다.');
}
res.redirect(redirectUrl);
}
@Get(':service/callback')
async oauthCallback(
@Param('service') service: string,
@Query('code') authorizedCode: string,
@Query('state') state: string,
@Res({ passthrough: true }) res: Response,
) {
const { username, accessToken, refreshToken } =
await this.authService.oauthCallback(service, authorizedCode, state);
if (username) {
res.cookie(`${service}Username`, username, {
path: '/',
httpOnly: true,
});
return { username };
}
res.cookie(JwtEnum.ACCESS_TOKEN_COOKIE_NAME, accessToken, {
path: '/',
httpOnly: true,
});
res.cookie(JwtEnum.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
path: '/',
httpOnly: true,
});
return { accessToken, refreshToken };
}
@Post(':service/signup')
async signUpWithOAuth(
@Param('service') service: string,
@Body('nickname') nickname: string,
@Req() req,
@Res({ passthrough: true }) res: Response,
) {
let resourceServerUsername: string;
try {
resourceServerUsername = req.cookies[`${service}Username`];
} catch (e) {
throw new UnauthorizedException('잘못된 접근입니다.');
}
const savedUser = await this.authService.signUpWithOAuth(
service,
nickname,
resourceServerUsername,
);
res.clearCookie(`${service}Username`, {
path: '/',
httpOnly: true,
});
return savedUser;
}
이렇게 하면 다음에 소셜 로그인 방식이 더 추가가 되더라도 API를 늘릴 필요가 없었다.
그래서 google 로그인까지 추가를 하는 것을 더 쉽게 할 수 있었다!
© 2023 debussysanjang
- 🐙 [가은] Three.js와의 설레는 첫만남
- 🐙 [가은] JS로 자전과 공전을 구현할 수 있다고?
- ⚽️ [준섭] NestJS 강의 정리본
- 🐧 [동민] R3F Material 간단 정리
- 👾 [재하] 만들면서 배우는 NestJS 기초
- 👾 [재하] GitHub Actions을 이용한 자동 배포
- ⚽️ [준섭] 테스트 코드 작성 이유
- ⚽️ [준섭] TypeScript의 type? interface?
- 🐙 [가은] 우리 팀이 Zustand를 쓰는 이유
- 👾 [재하] NestJS, TDD로 개발하기
- 👾 [재하] AWS와 NCP의 주요 서비스
- 🐰 [백범] Emotion 선택시 고려사항
- 🐧 [동민] Yarn berry로 모노레포 구성하기
- 🐧 [동민] Vite, 왜 쓰는거지?
- ⚽️ [준섭] 동시성 제어
- 👾 [재하] NestJS에 Swagger 적용하기
- 🐙 [가은] 너와의 추억을 우주의 별로 띄울게
- 🐧 [동민] React로 멋진 3D 은하 만들기(feat. R3F)
- ⚽️ [준섭] NGINX 설정
- 👾 [재하] Transaction (트랜잭션)
- 👾 [재하] SSH 보안: Key Forwarding, Tunneling, 포트 변경
- ⚽️ [준섭] MySQL의 검색 - LIKE, FULLTEXT SEARCH(전문검색)
- 👾 [재하] Kubernetes 기초(minikube), docker image 최적화(멀티스테이징)
- 👾 [재하] NestJS, 유닛 테스트 각종 mocking, e2e 테스트 폼데이터 및 파일첨부
- 2주차(화) - git, monorepo, yarn berry, TDD
- 2주차(수) - TDD, e2e 테스트
- 2주차(목) - git merge, TDD
- 2주차(일) - NCP 배포환경 구성, MySQL, nginx, docker, docker-compose
- 3주차(화) - Redis, Multer 파일 업로드, Validation
- 3주차(수) - AES 암복호화, TypeORM Entity Relation
- 3주차(목) - NCP Object Storage, HTTPS, GitHub Actions
- 3주차(토) - Sharp(이미지 최적화)
- 3주차(일) - MongoDB
- 4주차(화) - 플랫폼 종속성 문제 해결(Sharp), 쿼리 최적화
- 4주차(수) - 코드 개선, 트랜잭션 제어
- 4주차(목) - 트랜잭션 제어
- 4주차(일) - docker 이미지 최적화
- 5주차(화) - 어드민 페이지(전체 글, 시스템 정보)
- 5주차(목) - 감정분석 API, e2e 테스트
- 5주차(토) - 유닛 테스트(+ mocking), e2e 테스트(+ 파일 첨부)
- 6주차(화) - ERD
- 2주차(화) - auth, board 모듈 생성 및 테스트 코드 환경 설정
- 2주차(목) - Board, Auth 테스트 코드 작성 및 API 완성
- 3주차(월) - Redis 연결 후 RedisRepository 작성
- 3주차(화) - SignUpUserDto에 ClassValidator 적용
- 3주차(화) - SignIn시 RefreshToken 발급 및 Redis에 저장
- 3주차(화) - 커스텀 AuthGuard 작성
- 3주차(수) - SignOut시 토큰 제거
- 3주차(수) - 깃헙 로그인 구현
- 3주차(토) - OAuth 코드 통합 및 재사용
- 4주차(수) - NestJS + TypeORM으로 MySQL 전문검색 구현
- 4주차(목) - NestJS Interceptor와 로거
- [전체] 10/12(목)
- [전체] 10/15(일)
- [전체] 10/30(월)
- [FE] 11/01(수)~11/03(금)
- [전체] 11/06(월)
- [전체] 11/07(화)
- [전체] 11/09(목)
- [전체] 11/11(토)
- [전체] 11/13(월)
- [BE] 11/14(화)
- [BE] 11/15(수)
- [FE] 11/16(목)
- [FE] 11/19(일)
- [BE] 11/19(일)
- [FE] 11/20(월)
- [BE] 11/20(월)
- [BE] 11/27(월)
- [FE] 12/04(월)
- [BE] 12/04(월)
- [FE] 12/09(금)
- [전체] 12/10(일)
- [FE] 12/11(월)
- [전체] 12/11(월)
- [전체] 12/12(화)