뜨개질을 주제로 한 sns를 만드는 프로젝트를 진행중이다.
뜨개질의 경우 해외에 유명한 뜨개 사이트인 ravelry라는 사이트가 존재한다.
ravelry라는 사이트는 다양한 api를 제공해 주어 실, 도안에 대한 정보를 가져올 수 있다.
꽤 많은 정보를 제공해 활용도가 높아보이니 관심있다면 api docs를 한번 읽어보시길
Ravelry
www.ravelry.com
그런데 이 ravelry에서는 무료 oauth기능또한 제공하는데 ,,
아무래도 뜨개질 사이트기 때문에 ravelry 로그인 기능을 넣게 되었다.
카카오, 네이버, 구글과 같이 유명한 사이트의 oauth의 경우 passport라는 잘 만들어진 라이브러리가 존재한다.
그러나 ravelry는 passport 존재하지 않았기에, , 나는 다른 oauth 라이브러리를 사용해야 했다.
이 글을 통해 얻을 수 있는 것
- 백엔드에서 simple oauth를 통해 사용자의 정보를 받아오는 방법
- 백엔드에서 3자와 통신하여 사용자의 정보를 받아올 때 ,프론트에 user 정보를 전달하는 방법
왜 simple-oauth2를 사용했는가
이유는 간단했다.
ravelry api docs에는 oauth 샘플 코드를 제공하고 있다.
여기에서 simple-oauth2 를 사용하고 있어 나도 이 패키지를 선택했다.
https://glitch.com/~ravelry-test-oauth
그런데 simple oauth 사용법이 좀 달라진건지, 샘플 코드를 그냥 붙여서 사용했을 때 동작하지 않아 이것저것 좀 고쳐줬다.
아래에 고쳐서 돌게 만든 코드를 설명하도록 하겠다.
simple-oauth 패키지 npm 페이지
https://www.npmjs.com/package/simple-oauth2
ravelry oauth 설정 법
간단하게 ravelry oauth를 위해 설정해야 하는 과정을 설명해 보겠다.
다른 oauth와 같이 우선 raverly 사이트에 가서 client id 와 secret 을 발급받아야 한다.
우선 ravelry 개발자 센터에 개발자 등록을 한다.
https://www.ravelry.com/pro/developer
등록을 마치면 my apps 페이지에 가서 앱을 만들어 준다.
필요한 조건을 선택하여 앱을 생성하면 된다.
나는 OAuth 2.0을 선택했다.
그리고 생성한 앱의 설정에 들어가면 아래와 같이 나오게 된다.
그럼 여기서 Authorized Redirect Url에 callback을 받을 백엔드 서버 url을 넣어주면 된다.
한줄당 하나씩이고 나는 로컬과 , 배포된 서버 url 두개를 넣어줬다.
https://localhost:4040/api/v1/auth/ravelry/callback
${{배포 서버 url}}api/v1/auth/ravelry/callback
그럼 이제 client id와 secret을 확인할 수 있다.
이 값들은 나중에 넣어서 사용하면 된다.
구현
구현 설명에 앞서 프로젝트에서 선택한 oauth 구조를 간단히 설명해 보겠다.
프론트엔드에서는 로그인 버튼만 누르면 되도록 설정을 했다.
즉 oauth를 통해 사용자 정보를 받아오고 서버에 저장하는 모든 과정을 백엔드에서 진행토록 했다.
나는 ravelry를 이용한 로그인, 회원가입등의 로직을 구현하기 위해 auth-ravelry라는 nest resource를 만들었다.
키를 등록하고 인증하면 모든 과정을 진행하고 유저 정보를 반환해 주는 passport와 다르게 simple-oauth2 패키지를 사용하면 callback으로부터 전달받은 authorizationCode를 이용하여 accessToken을 발급하고 유저 정보를 받아오는 과정을 구현해 주어야 한다.
일단 OAuth는 아래 순서의 흐름을 가지며 흐름에 맞게 하나씩 설명하며 이에 해당하는 코드를 보여주도록 하겠다.
- redirect url을 발행하여 소셜 로그인 창을 띄워준다.
- 사용자가 소셜 로그인을 완료했을 때 callback을 통해 authorizationCode를 받는다.
- authorizationCode를 통하여 accessToken을 발급받는다.
- 발급받은 accessToken을 이용하여 유저 정보를 받아온다.
1. redirect url을 발행하여 소셜 로그인 창을 띄워준다.
프론트엔드에서 ravelry 로그인 버튼을 누르면 이런식으로 동의 화면을 띄워줘야 한다.
백엔드에서 이 화면으로 접근하기 위한 url을 제작하여 프론트로 리턴해 주어야 한다.
이 과정은 프론트에서도 할 수 있지만 , 모든 로직을 백엔드로 몰아 프론트에서는 설정할 필요가 없도록 백에서 구현하였다.
auth-ravelry.controller.ts
export class AuthRavelryController {
constructor(
public authService: AuthService,
public authRavelryService: AuthRavelryService,
private configService: ConfigService,
) {}
@Get('register')
@HttpCode(HttpStatus.MOVED_PERMANENTLY)
@ApiResponse({
status: 301,
type: AuthSocialLoginUrlDto,
description:
'Ravelry 로그인 창 url을 보내줍니다. 인증을 마치면 {{front_api_url}}/sign-in/social/${userId}으로 리다이렉트 됩니다.',
})
async register() {
const authorizationUri = await this.authRavelryService.getRedirectUrl();
return authorizationUri;
}
... 중략
}
auth-ravelry.service.ts
const {
AuthorizationCode,
} = require('simple-oauth2');
const randomString = require('randomstring');
async getRedirectUrl() {
const client = this.getClient();
const authorizationUri = client.authorizeURL({
redirect_uri: process.env.RAVELRY_CALL_BACK_URL,
scope: 'patternstore-read',
state: randomString.generate(),
});
return authorizationUri;
}
simple oauth2에서는 이런식으로 authorizeURL을 생성하는 기능을 제공한다.
나는그냥 redirect_uri, scope , state만 넣어주면 된다.
여기서 고민되는것이 scope와 state를 어떻게 넣느냐인데 ,
우선 state는 저렇게 randomstring을 넣어주어야 한다.(중요)
이러지않으면 생성된 url에 접속하면 유효하지 않다는 창이 뜨게 된다
그리고 scope는 내가 원하는 scope 이름을 띄어쓰기로 구분해서 넣어주면 된다.
이렇게 작성해주면 우리는 이제 callback 함수만 작성하면 된다.
우선 전체 callback함수를 통해 어떤 일을 하는지 간략하게 알아보고, oauth 구현 과정을 하나씩 상세하게 설명해 보도록 하겠다.
auth-ravelry.controller.ts
@Get('callback')
async callback(@Req() req, @Res() res) {
const accessToken = await this.authRavelryService.getAccessToken(req);
const ravelryUserInfo =
await this.authRavelryService.getUserInfoByAccessToken(accessToken);
const ravelryUser = await this.authRavelryService.findOrCreateRavelryUser(
ravelryUserInfo,
accessToken,
);
const socialData: SocialInterface =
this.authRavelryService.genSocialData(ravelryUser);
const userId = await this.authService.findOrCreateUserByRavelryUserId(
ravelryUser,
socialData,
);
const frontUrl = this.configService.get('FRONTEND_DOMAIN');
res.redirect(`${frontUrl}/sign-in/social/${userId}`);
}
callback함수에서는 크게 두가지 일을 한다.
1. 라이벌리로부터 유저 정보를 받아 조회해 보고, 없으면 생성하고 있으면 반환해준다.
2. 반환된 user의 index 값을 프론트로 반환해 준다.
백엔드에서 소셜 로그인을 구현하며 고민되는 것은
서버는 무작위로 콜백을 받아 유저의 정보를 저장한다.
유저를 저장하는것은 문제가 없지만 , 저장된 유저의 정보를 어디로 반환해 줄지를 알 수 가 없다.
이에 대한 해답으로 찾은것이 redirect를 통한 방법이다.
프론트엔드와 약속된 url에 user의 index를 param으로 보내준다.
그럼 프론트엔드에서는 이 user Id를 가지고 백엔드로 user 정보를 달라는 요청을 보내고,
그럼 백에서는 이 요청을 받아 user 정보를 반환해 주면 된다.
이 로직은 구글, 네이버, 카카오 모두 동일하여 auth.controller에 구현했다.
auth.controller.ts
@Post('social/login')
@ApiResponse({
status: 200,
type: AuthSocialLoginResDto,
description: '소셜 로그인 로그인 로직',
})
public async socialLogin(
@Body() authSocialLoginIngDto: AuthSocialLoginIngDto,
) {
const getUserAuthInfoByUserIdResult =
await this.service.getUserAuthInfoByUserId(authSocialLoginIngDto.userId);
const { token, refreshToken, user } = getUserAuthInfoByUserIdResult;
const resJson: AuthSocialLoginResDto = {
jwtToken: token,
refreshToken,
user: user,
};
return resJson;
}
대충 userId를 이용하여 jwt 토큰을 발급하고 user를 반환해 준다는 내용이다.
다시 oauth 과정으로 돌아와 simple ouath를 이용해 ravelry에서 user 정보를 얻어오는 코드를 살펴보자.
앞에서 1번 과정은 마쳤으니 여기서는 2,3,4 과정을 구현하면 된다.
위에 올린 auth-ravelry.controller.ts의 함수를 하나씩 뜯어가며 설명해 보겠다.
2. 사용자가 소셜 로그인을 완료했을 때 callback을 통해 authorizationCode를 받는다.
3. authorizationCode를 통하여 accessToken을 발급받는다.
auth-ravelry.controller.ts
@Get('callback')
async callback(@Req() req, @Res() res) {
const accessToken = await this.authRavelryService.getAccessToken(req);
... 생략
auth-ravelry.service.ts
async getAccessToken(req) {
const client = this.getClient();
const tokenParams = {
code: req.query.code,
redirect_uri: process.env.RAVELRY_CALL_BACK_URL,
scope: 'offline patternstore-read',
};
const getTokenResult = await client.getToken(tokenParams, { json: true });
const accessToken = getTokenResult.token.access_token;
return accessToken;
}
사용자가 소셜 로그인을 완료했을 때 callback을 통해 authorizationCode를 받는데, 이는 req.query.code를 통해 접근이 가능하다.
발급받은 authorizationCode를 통하여 accessToken을 발급받는 과정은 위와 같다.
그냥 simple oauth를 사용하면 쉽게 발급받을 수 있다.
4. 발급받은 accessToken을 이용하여 유저 정보를 받아온다.
이제 발급받은 accessToken을 이용하여 유저 정보를 받아와보자.
auth-ravelry.controller.ts
const ravelryUserInfo =
await this.authRavelryService.getUserInfoByAccessToken(accessToken);
auth-ravelry.service.ts
async getUserInfoByAccessToken(accessToken) {
const requestConfig = {
headers: {
Authorization: 'Bearer ' + accessToken,
},
params: {},
};
const request = this.http
.get('https://api.ravelry.com/current_user.json', requestConfig)
.pipe(
catchError((error: AxiosError) => {
throw 'ravelry get user error!';
}),
);
const { data } = await firstValueFrom(request);
const ravelryUserInfo: ravelryUserDto = data.user;
return ravelryUserInfo;
}
ravelry는 auth baear token을 사용한다. 이에 맞춰서 config를 작성해주고,,
요청을 보내면 된다.
이 과정을 성공했을 때에는 data.user를 통해 user 정보를 받을 수 있으며 그 user에 담긴 내용은 다음과 같다.
이렇게 하면 redirect url 생성부터, user 정보 받아오기까지 oauth에서 해야할 일을 모두 마쳤다.!!
결론
이번 포스트에서는 NestJS에서 simple-oauth2 패키지를 사용하여 Ravelry 로그인을 구현하는 방법에 대해 알아보았다. 사실 express로 구현해도 거의 비슷하게 구현할 것이므로 node js 에서 simple-oauth2를 이용하여 oauth 구현을 했다고 보면 되겠다.
휙 넣으면 휙 유저정보를 가져다주는 passport와는 달리 oauth의 과정을 이해하고 순서에 맞게 하나씩 과정을 구현해야하는 점이 조금은 어려웠지만 오히려 oauth에 대해 지식을 정리할 기회를 주었다.
정보가 별로 없는 nest js에서 더 정보가 없는 듣보 ravelry oauth로그인 구현을 위한 자료가 없었기에,,
내 글이 누군가에게 도움이 되길 바라며 남겨본다.
다음에는 ravelry 연동과, 로그인을 구분해서 구현한 법을 작성해 보고자 한다.
'NodeJS' 카테고리의 다른 글
typeorm left join시 soft delete 된 값 포함하는 법 (0) | 2023.05.16 |
---|---|
카카오 로그인 구현 KOE101에러 해결( 앱키 문제 아니였음 ) (1) | 2023.04.22 |
sequlize raw 쿼리와 orm을 통한 쿼리를 동시에 사용법 (시퀄라이즈 컬럼간 비교) (0) | 2023.01.27 |
sequelize CASE WHEN 으로 정렬하기 (order CASE WHEN) (0) | 2023.01.03 |
passport local CORS (프론트, 백 분리 시 sid 쿠키 저장 안되는 현상 해결) (2) | 2022.10.14 |