GitHub 로그인, 회원가입 구현하기
- 깃헙 소셜 로그인은 OAuth 표준을 따른다.
- 다른 소셜 로긴도 OAuth 를 따르니 비슷하게 구현이 가능하다.
이 포스팅에 쓰인 코드는 노마드코더-캐럿마켓 클론코딩 에서 배운 코드를 참고했다.
단, 비슷하긴 하나, 완전히 동일하지 않고 몇몇 부분이 다르다.
GitHub 로그인, 회원가입 절차 살펴보기
시퀀스 다이어그램으로 살펴보기
sequenceDiagram
participant User as 사용자
participant App as 애플리케이션
participant Google as 구글 서버
User->>App: 구글 로그인 요청
App->>Google: 인증 코드 요청 (client_id, redirect_uri, scope)
Google->>User: 로그인 및 권한 승인 화면 표시
User->>Google: 로그인 및 권한 승인
Google->>App: 인증 코드 전달 (redirect_uri로)
App->>Google: 액세스 토큰 요청 (client_id, client_secret, 인증 코드)
Google->>App: 액세스 토큰 및 리프레시 토큰 전달
App->>Google: 사용자 정보 요청 (액세스 토큰 사용)
Google->>App: 사용자 정보 전달
App->>User: 로그인 완료 및 서비스 제공
깃헙 홈페이지에서 애플리케이션 등록하기
여기서 등록한 앱은 추후에 Settings / Developer Settings 에서 확인할 수 있다.
OAuth 의 흐름
사실 흐름 자체는 맨 위에 있는
APP
,GITHUB
과 화살표가 있는 이미지로 이해하는 게 더 이해하기 쉽다.
- 사용자가 애플리케이션에 접속함
- 사용자를 GitHub 으로 이동시키고 인증을 요청함
- 사용자가 GitHub 아이디를 입력하고 인증을 완료함
- 사용자는 GitHub 에 의해 다시 해당 애플리케이션으로 리다이렉션됨
- 이제 애플리케이션은 사용자의 액세스 토큰으로 GitHub API 에 엑세스함
1단계: 사용자를 GitHub 으로 이동시키고 인증 요청하기
- 사용자를 깃허브의 인증 페이지로 보낸다.
- 인증페이지로 보내면서, 내 앱의
Client ID
,Scope
, 기타 옵션들을 파라미터로 함께 전달한다.
GET https://github.com/login/oauth/authorize
라우터 구현하기
- 라우터를 구현하여 사용자를 GitHub 으로 이동시키고 인증을 요청할 것이다.
- 라우터를 구현 안하고 그냥 파라미터와 함께 사용자를 넘겨버려도 어차피
callback URL
로 돌아오게 되어있으니 사실 상관없다.
- 라우터를 구현 안하고 그냥 파라미터와 함께 사용자를 넘겨버려도 어차피
client_id
,scope
,allow_signup
을 파라미터로 제공함과 동시에 사용자를 깃허브 인증페이지로 보낸다.- 인증이 완료되면 깃허브 애플리케이션을 생성할 때 세팅해둔
callback URL
로 돌아오게 된다.- 나의 경우 아래 스크린샷에 보이듯 사용자는
http://localhost:3000/github/complete
로 돌아오게 된다.
- 나의 경우 아래 스크린샷에 보이듯 사용자는
import { NextResponse } from "next/server";
export function GET() {
const baseUrl =
"https://github.com/login/oauth/authorize";
// 파라미터의 프로퍼티와 관련 정보 링크
// https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
const params = {
client_id: process.env.GITHUB_CLIENT_ID!,
// redirect_uri: Github 페이지에서 이미 세팅했기 때문에 필요 없음,
scope: "read:user,user:email", // 필요한 부분만 요청하기
// 스코프 설명 관련 링크: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps
allow_signup: "true", // GitHub 비회원이더라도 바로 가입해서 서비스를 이용할 수 있도록하기
};
const formattedParams = new URLSearchParams(
params
);
const redirectUrl = `${baseUrl}?${formattedParams}`;
return NextResponse.redirect(redirectUrl);
}
2단계: Code 파라미터를 AccessToken 으로 교환하기
- 사용자는 이전에 앱에 설정해두었던 callback URL 로 돌아오게 되는데,
code
라는 파라미터 값과 함께 돌아온다.
액세스 토큰 받기
https://github.com/login/oauth/access_token
에 POST 요청을 보내고 위에서 받은 CODE 를 파라미터 값으로 보내면 액세스 토큰을 얻을 수 있다.- 깃허브 공식 문서 에 자세한 내용이 적혀있다.
import { notFound } from "next/navigation";
import {
NextRequest,
NextResponse,
} from "next/server";
import axios from "axios";
export async function GET(request: NextRequest) {
const code =
request.nextUrl.searchParams.get("code");
if (!code) {
return notFound();
}
const accessTokenUrl =
"https://github.com/login/oauth/access_token";
const headers = {
Accept: "application/json",
};
const params = {
client_id: process.env.GITHUB_CLIENT_ID!,
client_secret:
process.env.GITHUB_CLIENT_SECRET!,
code,
};
const accessTokenResponse = await axios.post(
accessTokenUrl,
params,
{
headers,
}
);
const accessTokenData =
accessTokenResponse.data;
if (accessTokenData.error) {
return NextResponse.json(
{
message:
"인증에 실패하였습니다. 다시 시도해주세요",
},
{
status: 400,
}
);
}
return NextResponse.json({ accessTokenData });
}
깃허브 API 에 회원정보 요청하기
- 발급받은 액세스 토큰을 이용하여 GitHub API 에 회원정보를 요청할 수 있다.
- 인증한 유저의 깃허브 로그인 아이디, 닉네임, 팔로워 등의 회원정보를 얻을 수 있다.
Authorization: Bearer OAUTH-TOKEN
GET https://api.github.com/user
API 로 얻은 회원정보를 통해 로그인 혹은 회원가입 처리하기
- 받게된 회원정보를 기반으로 로그인 혹은 회원가입 처리를 하면 된다.
- 해당 github ID 로 이미 가입된 계정이 있다면 로그인을 수행한다.
- 해당 github ID 로 가입된 내역이 없다면, 회원가입 후 로그인을 수행한다.
- 단, 회원가입 시에는 반드시 정책을 잘 정해야 한다.
- 깃허브 ID 를 그대로 유저 ID 와 동일하게 사용하면 문제가 된다.
- 디비에 GITHUB_ID 와 같은 컬럼을 만들고 거기다가 아이디를 기록하고 유저 ID 는 깃허브를 이용해 가입했음을 알 수 있는 방식으로 하는 것이 좋다.
- 나는 아래 소스코드에서
@GITHUB_ID
와 같은 방식으로 아이디를 저장해두었다.
- 나는 아래 소스코드에서
import {
notFound,
redirect,
} from "next/navigation";
import {
NextRequest,
NextResponse,
} from "next/server";
import axios from "axios";
import db from "@/lib/db";
import { loginByUserId } from "@/lib/session";
interface IAccessTokenData {
access_token?: string;
token_type?: string;
scope?: string;
error?: string;
error_description?: string;
error_url?: string;
}
export async function GET(request: NextRequest) {
const code =
request.nextUrl.searchParams.get("code");
if (!code) {
return notFound();
}
const accessTokenUrl =
"https://github.com/login/oauth/access_token";
const headers = {
Accept: "application/json",
};
const params = {
client_id: process.env.GITHUB_CLIENT_ID!,
client_secret:
process.env.GITHUB_CLIENT_SECRET!,
code,
};
const accessTokenResponse = await axios.post(
accessTokenUrl,
params,
{
headers,
}
);
const { data }: { data: IAccessTokenData } =
accessTokenResponse;
if (!data || data.error) {
return NextResponse.json(
{
message:
"인증에 실패하였습니다. 다시 시도해주세요",
},
{
status: 400,
}
);
}
// fetch 를 사용하는 경우, no-cache 옵션이 필요하다.
const { data: gitUser } = await axios.get(
"https://api.github.com/user",
{
headers: {
Authorization: `Bearer ${data.access_token}`,
},
}
);
const { id, avatar_url, login } = gitUser;
// 가입된 회원이 있는지 찾기
const user = await db.user.findUnique({
where: {
github_id: login,
},
select: {
id: true,
},
});
// 가입된 회원이 있다면 로그인 처리 후 메인페이지 이동
if (user) {
await loginByUserId(user.id);
return redirect("/");
}
// 가입된 회원이 없다면 회원가입 처리 후 profile 페이지 이동
const newUser = await db.user.create({
data: {
github_id: login,
avatar: avatar_url,
username: `@GITHUB_${id}`,
},
});
await loginByUserId(newUser.id);
return redirect("/profile");
}
반응형
'프레임워크 > next.js' 카테고리의 다른 글
Next.js API 핸들러 커스텀 미들웨어 구성하기 (0) | 2022.07.16 |
---|---|
next.js import 경로 예쁘게 하기 (0) | 2022.07.05 |
Next.js 의 API routes (0) | 2022.07.04 |
next.js 의 Dynamic Routes (동적 라우트) (0) | 2022.06.24 |
Next.js getServerSideProps 서버사이드 렌더링 알아보기 (0) | 2022.06.24 |