개발

github oauth2 클라이언트와 서버 구현해보기 -1

배우겠습니다 2024. 2. 12. 17:06

사용하는것

next14

nest

passport

github oauth

 

환경

frontend: localhost:3001

backend: localhost:3000

 

구현해야 하는것

우린 github oauth을 서비스에 이용한다. github profile을 이용해 서비스를 이용할 수 있게한다.

 

0. github에 어플리케이션을 등록한다.

https://github.com/settings/applications

 

GitHub: Let’s build from here

GitHub is where over 100 million developers shape the future of software, together. Contribute to the open source community, manage your Git repositories, review code like a pro, track bugs and fea...

github.com

clientid clientsecret을 발급받는다.

Homepage Url은 http://localhost:3001 로 설정한다.

Authorization callback URL은 http://localhost:3000/auth/github/callback 로 설정한다.

 

1. next frontend에선 github oauth server로 client_id를 담아 이동한다.

Next/Link컴포넌트를 이용한다. (pure react는 a태그)

import Image from "next/image";
import Link from "next/link";
const GithubSection = () => {
  const clientId = process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID;
  const url = `https://github.com/login/oauth/authorize?client_id=${clientId}`;
  return (
      <Link
        href={url}
      >
        <Image
          src="/image/github-logo.png"
          alt="github"
          width={30}
          height={30}
        />
        깃허브로 로그인
      </Link>
  );
};

export default GithubSection;

 

중요한 것은 민감한 정보(cliendId)를 환경변수로 사용하는 것이다.

//.env.local
NEXT_PUBLIC_GITHUB_CLIENT_ID=발급받은 클라이언드 id

 

프로젝트 루트의 .env.local에 다음과 같이 작성한다.

NEXT_PUBLIC_ 을 앞애 붙여줘야한다. 아니면 undefined가 나온다.

이제 프론트는 백엔드가 열심히 일하는 것을 기다린다.

 

2. 그럼, 깃허브는 등록한 http://localhost:3000/auth/github/callback에 쿼리 파라미터로 code를 담아줄 것이다.

이 코드는 엑세스토큰을 github에 요청하고 발급받는데 사용된다.

 

3. nest backend에 환경 변수를 설정해준다.

// .env
GITHUB_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CALLBACK_URL=
JWT_SECRET=

필요한 값을 채워준다.

app.module에 환경변수 설정을해준다.

//app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot(envConfig),
  ],
})
export class AppModule {
  constructor(private dataSource: DataSource) {}
}
//envConfig
import { ConfigModuleOptions } from '@nestjs/config';

export const envConfig: ConfigModuleOptions = {
  내 맘대로 설정
  나같은 경우는 isGlobal: true,옵션을 사용한다.
  또한 .env가 아닌 별도 환경변수 파일을 사용할시 envFilePath도 설정해준다.
};

 

4. github passport strategy를 작성해준다.

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { Profile, Strategy } from 'passport-github';

@Injectable()
export class GithubStrategy extends PassportStrategy(Strategy, 'github') {
  constructor(configService: ConfigService) {
    super({
      clientID: configService.get<string>('GITHUB_CLIENT_ID'),
      clientSecret: configService.get<string>('GITHUB_CLIENT_SECRET'),
      callbackURL: configService.get<string>('GITHUB_CALLBACK_URL'),
      scope: ['public_profile'],
    });
  }

  async validate(
    accessToken: string,
    _refreshToken: string,
    profile: Profile,
    // done: any,
  ) {
  	// 필요한 validate로직
    return profile;
  }
}

 

설정한 환경변수로 strategy를 초기화해준다.

validate로직을 작성해준다.

(passport에 대해서 설명하는 것도 한 포스트가 될 것같아 자세한건 다른 포스트에 쓸 예정이다.)

참고: https://www.passportjs.org/packages/passport-github/

 

5. controller를 작성해준다.

import { Controller, Get, Redirect, Req, Res, UseGuards } from '@nestjs/common';
import { Request, Response } from 'express';
import { JwtAuthService } from '../jwt/jwt-auth.service';
import { AuthGuard } from '@nestjs/passport';

@Controller('auth/github')
export class GithubController {
  constructor(private jwtAuthService: JwtAuthService) {}
  // @Get()
  // @UseGuards(AuthGuard('github'))
  // async login() {}

  @Get('callback')
  @UseGuards(AuthGuard('github'))
  async githubAuthCallback(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ) {
    const user = req.user;
    return user
  }
}

 

6. module을 만들어준다.

@Module({
  imports: [],
  controllers: [GithubController],
  providers: [GithubStrategy],
})
export class GithubModule {}

 

 

이제 http://localhost:3000/auth/github/callback에서 어떤일이 벌어질까.

UseGuards(AuthGuard('github'))이 해당 컨트롤러에서 github module에 주입된 github strategy 로직을 사용하게 해준다.

passport는 oauth2 프로세스에 따라 다음을 수행한다.

1. auth/gihub/callback?code=에서 코드를 추출한다.

2. 엑세스 토큰을 github에 요청 후 받는다.

3. 이제 엑세스토큰과 github profile정보를 사용할 수 있다.

이것을 우리가 일일히 작성할 필요없이 passport한테 맡기면 된다.

github strategy는 validate로 검증 후 req에 유저정보를 반환할 것이다.(위 코드에선 작성한 로직이 별거없으므로 단순 githubprofile정보가 반환된다.)

 

다음

다음 포스트에선 passport나 이를 jwt와 연동하는걸 해볼 생각이다.

우선, githubAuthCallback에서 로그인이 성공했거나 실패했다면 res.redirect로 localhost:3001의 특정 라우트로 라다이렉트 시키는 코드가 필요하다. 프론트와 협의해서 어디로 리다이렉트 시킬지 결정해본다. 

이는 생략한다.