인프런 커뮤니티 질문&답변

노른자님의 프로필 이미지
노른자

작성한 질문수

Slack 클론 코딩[백엔드 with NestJS + TypeORM]

@nestjs/passport

소셜 로그인 질문

작성

·

1.4K

0

저는 passport-jwt를 사용하여 로그인을 구현하였는데,

passport-apple 를 사용하여 소셜 로그인 구현 중에 막혔습니다.

 아무리 구글링해봐도 passport-apple + nest.js 조합이 없어 해결이 되지않아 질문드립니다.

우선 제가 이해하고있는 전반적인 큰 흐름부터 맞나 의심됩니다.

1. passport-apple 을 통하여 유저 정보를 받아온 후

2. 유저에게 jwt를 발급

3. 유저는 클라이언트단(or 쿠키)에 jwt 저장

4. 이후 애플서버와는 연계없이 jwt를 이용해 제 서버만을 통하여 유저 정보를 줌.

이게 맞나 모르겠고 틀린 점이 있으면 지적해주시면 감사하겠습니다!

 

그리고 본 질문은 passport-apple을 통하여 어떻게 유저 정보를 받아오는지를 모르겠습니다.

 

node.js passport-apple 공식문서를 통해 유추하며 해봤습니다

/apple.strategy.ts 파일

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-apple';

@Injectable()
export class AppleStrategy extends PassportStrategy(Strategy,'apple') {
constructor() {
super({
clientID: process.env.APPLE_CLIENT_ID,
teamID: process.env.APPLE_TEAM_ID,
callbackURL: process.env.APPLE_CALLBACK_URL,
keyID: process.env.APPLE_KEY_ID,
privateKeyString: process.env.APPLE_KEY,
//참고로 privateKeyLocation로도 해보고
// passReqToCallback: true도 넣어봤습니다. (뭔진 모르겠지만)
});
}

async validate(req,accessToken, refreshToken, idToken, profile, done) {
console.log('req :', req);
console.log('idToken :', idToken);
console.log('accessToken :', accessToken);
console.log('refreshToken :', refreshToken);
console.log('profile :', profile);
done(null,idToken)
}
}

super에 필요객체를 넣고 validate 인자로 어떤값들이 들어오는지 하나하나 로그를 찍어보려했으나, 아예 저 부분으로 넘어가지않고 에러가 뜹니다.

 

/auth.module.ts 파일

import { JwtStrategy } from './strategies/jwt.strategy';
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from 'src/common/entities/user.entity';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { AppleStrategy } from './strategies/apple.strategy';

@Module({
imports: [
TypeOrmModule.forFeature([User]),
PassportModule.register({ session: false }),
JwtModule.register({
secret: process.env.JWT_SECRET_KEY,
signOptions: { expiresIn: '60s' },
}),
],
providers: [AuthService, JwtStrategy, AppleStrategy],
controllers: [AuthController],
})
export class AuthModule {}

 

/auth.controller.ts 파일

import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { Controller, Get, UseGuards, Req, Post, Body } from '@nestjs/common';

@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}

@Get('/apple')
@UseGuards(AuthGuard('apple'))
async appleLogin() {}
 
@Post('/apple/callback')
@UseGuards(AuthGuard('apple'))
async appleLoginCallback(@Req() req, @Body() body) {
console.log("req : ",req)
console.log('body : ',body)
return 'ok';
}
}

 

전 강의영상의 LocalAuthGuard 같은 클래스 파일을 따로 만들지 않고 바로  AuthGuard('apple')을 데코레이터에 넣어주는 식올 구현했습니다. 

여기서 "도메인/auth/apple" 로 접속시 애플 로그인 화면이 뜨는 것까지는 되는데, 로그인을 후 "계속" 버튼을 누르면 

서버에 이러한 500에러가 뜹니다

[Nest] 58  - 02/05/2022, 9:56:26 AM   ERROR [ExceptionsHandler] Failed to obtain access token

InternalOAuthError: Failed to obtain access token

    at AppleStrategy.OAuth2Strategy._createOAuthError (/usr/src/app/node_modules/passport-oauth2/lib/strategy.js:423:17)

    at /usr/src/app/node_modules/passport-oauth2/lib/strategy.js:177:45

    at /usr/src/app/node_modules/passport-apple/src/strategy.js:101:13

    at processTicksAndRejections (node:internal/process/task_queues:96:5)

[Nest] 58  - 02/05/2022, 9:56:26 AM     LOG [HTTP] 500 POST /api/auth/apple/callback - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 ::ffff:172.18.0.3

도무지 모르겠어서 2번째 (Post부분) @UseGuard데코레이터를 빼서 결과를 봐보니 body에 

{state: '2ed****e0ed',

 code: 'ce23f9c72e7904******d8565b94b4a7.0.r**z.5nDn7h*******RJufk11LWQ'}

이러한 객체가 담겨옵니다. 제 예측으로는 이것이 access 토큰인것같고 이걸 다시 애플에 보내 유저정보를 받아내야하는것같은데, 코드를 어떻게 구현해야하는지 모르겠습니다...

혹시 실무에서 apple login을 어떻게 구현하셨는지, passport-apple이 안되거나 더 편한 다른방법이라도 있다면 알려주시면 정말 감사하겠습니다.

답변 1

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

일단 passport-jwt랑 passport-apple은 호환이 안 될겁니다. passport-apple이 jwt 토큰으로 로그인하는 방식이 아닌 걸로 알고 있습니다.

애플쪽에서도 로그인 서비스를 만드셔야 할텐데 만드셨나요? 그리고 현재 서버의 주소를 등록하셨나요?

노른자님의 프로필 이미지
노른자
질문자

네 애플에시 로그인 서비스를 만들었고, https 적용시킨 현재 서버 주소도 등록했습니다.

passport-apple 로 유저정보만 받아온뒤 db에 저장후 그것으로 jwt를 발급한뒤 이후로는 passport-jwt를 사용하여 인증하려고 합니다.

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

아, 지금보니까 /apple/callback이 post네요. get이어야될겁니다. /apple/callback 주소도 애플서비스에 등록하셨죠?

노른자님의 프로필 이미지
노른자
질문자

예 redirectURL로 등록하였습니다. 다만 GET으로도 해본결과 

LOG [HTTP] 404 POST /api/auth/apple/callback - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 ::ffff:172.18.0.3

404 POST가 뜨는걸로보아하니 Post로 오는것같습니다.

노른자님의 프로필 이미지
노른자
질문자

혹시라도 여기서 잘못됐을수도있으니 말씀드려봅니다.

AWS EC2 사용중이며 앞단에 리버스프록시 nginx를 넣고 backend 서버를 넣고 도커로 감싼형태입니다.

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

음, 저기서 POST로 찍히는 것으로 보아서는 애플이 post로 쏘는 게 맞나보네요.

https://github.com/ananay/passport-apple/issues/33

같은 질문이 있는데

https://github.com/ananay/apple-auth/blob/master/SETUP.md

에 따라서 세팅이 문제였다고밖에 생각되지 않긴 합니다.

 

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

소스코드 까보니

https://github.com/ananay/passport-apple/blob/master/src/strategy.js#L61-L65

이 줄이 문제인건데 해당 옵션들에 문제가 있는 것 같습니다.

노른자님의 프로필 이미지
노른자
질문자

흠.. 분명히 설정은 똑바로 돼있는데, 밑에 privateKeyLocation과 privateKeyString을 둘다 넣어줘야하는걸까요

const _tokenGenerator = new AppleClientSecret({
"client_id": options.clientID,
"team_id": options.teamID,
"key_id": options.keyID
}, options.privateKeyLocation, options.privateKeyString);


하나 걸리는건 privateKeyString을 .env에 넣을때 한줄 파일로 만들었는데

-----BEGIN PRIVATE KEY-----

MIGTAgEAMBMGByqGSM49AgE********wdwIBAQQgdtJipdkgPvvNyjK6

ZS0Bux9rK4ioPDbk********Izj0DAQehRANCAASqwfykwIPl8TmQ

wIR1NHHVtQPMRv*********4hokF/5m+SPo9kLLDuF/ieGc19CCp04Un

Yp***bE

-----END PRIVATE KEY-----

이랬던걸

-----BEGIN PRIVATE KEY-----\n

MIGTAgEAMBMGByqGSM49AgE********wdwIBAQQgdtJipdkgPvvNyjK6\n

ZS0Bux9rK4ioPDbk********Izj0DAQehRANCAASqwfykwIPl8Tm\n

wIR1NHHVtQPMRv*********4hokF/5m+SPo9kLLDuF/ieGc19CCp04Un\n

Yp***bE\n

-----END PRIVATE KEY-----

이런식으로 \n 을 붙여 한줄로 만들었습니다.

하지만 privateKeyLocation으로도 해봤기때문에, 이것때문은 아니라고 보이고

 

두번째로 의심되는건 scope인데, passport-apple 공식문서엔 scope적는 필드가 보이지 않아서 넣지않았습니다.

 

 

노른자님의 프로필 이미지
노른자
질문자

무튼 아무래도 답은 설정문제 밖에 없어보이네요... 차라리 다행이네요

 

이것저것 해보고 안되면 다시 질문드리겠습니다.

주말 늦은시간까지 도와주셔서 정말 감사합니다!

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

privateKeyString에서 줄바꿈 문제일 수 있으니 그건 빼시고 privateKeyLocation으로 키 위치를 지정하시는 게 더 나을 것 같습니다. (키는 따로 텍스트로 저장)

노른자님의 프로필 이미지
노른자
질문자

strategy 폴더 안에 키파일과 apple.strategy.ts를 같이 넣고,

privateKeyLocation: './AuthKey_T****99Y.p8',

를 추가해도 안되네요... 

도커때문인가,,,

ack:
build:
context: ./back
dockerfile: Dockerfile
restart: always
volumes:
- "./back:/usr/src/app"
- "/usr/src/app/node_modules"

node_modules는 마운트 안시킨게 문제되는건 아니겠죠?

 

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

location을 적으실 때 저렇게 적으면 문제가 될 수 있습니다. 반드시 path.join이나 path.resolve 메서드를 사용하셔야 합니다.

노른자님의 프로필 이미지
노른자
질문자

그렇군요! 바로 적용해보겠습니다

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

보니까 keyfile을 config 폴더에 넣으시는 것 같은데 path.join으로 config 폴더 가리키시면 될 것 같네요.

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

https://developoerty.tistory.com/125?category=853583

여기서는 privateKeyString 넣는 방법이 잘 나와있네요. `` 쓰시면 \n같은거 없이 하실 수 있습니다.

노른자님의 프로필 이미지
노른자
질문자

와 백틱으로 하니 됐습니다

[Nest] 76  - 02/05/2022, 3:15:56 PM   ERROR [ExceptionsHandler] done is not a function

TypeError: done is not a function

이제 이런에러가 뜨는거보니 validate쪽으로 넘어가나보네요!!

이것때문에 하루종일,,, 희열감과 허탈감이 공존하네요

정말 감사합니다 나머지는 혼자 할 수 있을것같습니다!

안녕하세요. 위에 토론 내용을 보고 저도 애플 로그인을 구현중인데요. 

 

validate 부분에서 accessToken, refreshToken 값은 정상적으로 받아오는데요.

 

idToken 값은 "{}" 으로 받아와 지네요. 혹시 idToken 값을 불러와야 그 속에 사용자 계정 정보에 접근할 수 있을 것 같은데 이 부분에 대해서 어떻게 처리해야 될지 답변을 요청 드려도 될까요? 

 

미리 감사드리며 부탁드립니다. ^^ 

노른자님의 프로필 이미지
노른자
질문자

이거 passport-apple 이 nestjs랑 호환문제가 있습니다. node_modules/passport-apple  소스코드 안으로 들어가서 코드를 조금 수정해야합니다.

 

https://github.com/pekosong/passport-apple

이 소스코드로 수정해보시면 될겁니다.

소중한 답변 감사드립니다. 참조해서 적용해보겠습니다. ㅠㅠ

저는 이것때문에 며칠동안 골머릴 쌓는데 노른자 님 덕분에 해결할 수 있을것 같습니다. 다시 한번 감사드립니다. 꾸벅!!

노른자 님 덕분에 저도 해결되었습니다. 감사드립니다. ㅠㅠ

노른자님의 프로필 이미지
노른자
질문자

몇주전 저를 보는듯하네요. 축하드립니다.

넵넵 감사합니다. ^^

혹시 어떻게 해결하셨는지 알수 있을까요? 위에 알려주신 깃헙 주소에 접근이 안되네요

nestjs의 validation에서 id_token이 없는 경우

같은 문제로 다른 분들 도움 되실까봐 공유 드립니다 !

 저는 해당 문제를 passport-apple을 fork하여 해결하였습니다.

 

https://github.com/kiJu2/passport-apple-for-nestjs/commit/5982b9428820a748aeab8232e13930c305d132eb

@노른자님 감사드립니다. 덕분에 문제 해결에 큰 도움이 되었습니다 !

 

추가로, validation은 다음과 같이 작성하였습니다.

async validate(
    req: any,
    accessToken: string,
    results: {
      id_token: string;
      refresh_token: string;
      expires_in: number;
      access_token: string;
      token_type: string;
    },
    profile: any,
    done: (error: any, user?: any, info?: any) => void,
  )
노른자님의 프로필 이미지
노른자

작성한 질문수

질문하기