OAuth 2.0이란?
↓ ↓ ↓ ↓ ↓
[Node.js] OAuth 2.0
OAuth 2.0이란? 사용자의 자원에 대한 액세스를 제한된 범위에서 제3자 애플리케이션이 안전하게 수행할 수 있도록 허용하는 인증 및 권한 부여 프로토콜 이 프로토콜은 사용자가 비밀번호와 같은
xuwon.tistory.com
카카오 로그인을 구현하기 위해서 우선 카카오 디벨로퍼에서 애플리케이션 등록을 해야 합니다!
등록 후 이 문서를 참고하며 구현해 보겠습니다.
Kakao Developers
카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.
developers.kakao.com
애플리케이션 등록 후
앱설정 - 앱키에서 REST API 키를 받아와 kakaoClientId로 저장하고,
카카오 로그인에서 Redirect URI를 등록해주면 끝!
// server.js
const express = require('express')
const cors = require('cors')
const axios = require('axios')
const kakaoClientId = 'REST API KEY'
const redirectURI = 'http://192.168.0.10:5500'
const app = express()
app.use(cors({
origin: ['http://localhost:5500', 'http://192.168.0.10:5500'],
methods: ["OPTIONS", "POST", "DELETE"],
}))
app.use(express.json())
app.post('/kakao/login', (req, res) => {
const authorizationCode = req.body.authorizationCode
axios.post('https://kauth.kakao.com/oauth/token', {
grant_type: 'authorization_code',
client_id: kakaoClientId,
redirect_uri: redirectURI,
code: authorizationCode
},
{
headers: {'Content-type': 'application/x-www-form-urlencoded;charset=utf-8'}
}
)
.then(response => res.send(response.data.access_token))
})
app.post('/kakao/userinfo', (req, res) => {
const { kakaoAccessToken } = req.body
axios.get('https://kapi.kakao.com/v2/user/me', {
headers: {
Authorization: `Bearer ${kakaoAccessToken}`,
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
}
})
.then(response => res.json(response.data.properties))
})
app.delete('/kakao/logout', (req, res) =>{
const {kakaoAccessToken} = req.body
axios.post('https://kapi.kakao.com/v1/user/logout', {}, {
headers: {
Authorization: `Bearer ${kakaoAccessToken}`
}
})
.then(response => res.send('로그아웃 성공'))
})
app.listen(3000, () => console.log('서버 열림!'))
// login.js
const kakaoLoginButton = document.querySelector('#kakao')
const userImage = document.querySelector('img')
const userName = document.querySelector('#user_name')
const logoutButton = document.querySelector('#logout_button')
let currentOAuthService = ''
function renderUserInfo(imgUrl, name) {
userImage.src = imgUrl
userName.textContent = name
}
const kakaoClientId = 'REST API KEY'
const redirectURI = 'http://192.168.0.10:5500'
let kakaoAccessToken = ''
kakaoLoginButton.onclick = () => {
location.href = `https://kauth.kakao.com/oauth/authorize?client_id=${kakaoClientId}&redirect_uri=${redirectURI}&response_type=code`
}
window.onload = () => {
const url = new URL(location.href);
const urlParams = url.searchParams
const authorizationCode = urlParams.get('code');
if (authorizationCode) {
axios.post('http://localhost:3000/kakao/login', {
authorizationCode
})
.then(res => {
kakaoAccessToken = res.data
return axios.post('http://localhost:3000/kakao/userinfo', { kakaoAccessToken })
})
.then(res => {
renderUserInfo(res.data.profile_image, res.data.nickname)
currentOAuthService = 'kakao'
})
}
};
logoutButton.onclick = () => {
axios.delete('http://localhost:3000/kakao/logout', {
data: { kakaoAccessToken }
})
.then(res => {
console.log(res.data)
renderUserInfo('', '')
})
}
}
카카오 로그인 흐름
1. 사용자를 카카오 로그인 페이지로 리다이렉트
2. 인가 코드 받기
3. 액세스 토큰 발급 요청
4. 사용자 정보 요청
1. 사용자를 카카오 로그인 페이지로 리다이렉트
인가 코드를 받으려면 GET 요청을 보내야 합니다.
쿼리 파라미터로 client_id와 redirect_uri, response_type을 넣어달라고 하네요!
response_type은 code로 고정하여 보내면 된다고 합니다.
// login.js
kakaoLoginButton.onclick = () => {
location.href = `https://kauth.kakao.com/oauth/authorize
?client_id=${kakaoClientId}&redirect_uri=${redirectURI}&response_type=code`
}
location.href는 사용자를 특정 URL로 리다이렉트 합니다.
따라서 카카오 로그인 버튼을 클릭하게 되면, 인증 화면으로 리다이렉트 됩니다.
리다이렉트 되면서 GET 요청이 보내진다고 합니다!
2. 인가 코드 받기
인증 화면에서 인증을 마치고 나면, 지정한 RedirectURI로 돌아오게 됩니다.
http://192.168.0.10:5500/?code=AuthorizationCode
이렇게 쿼리 파라미터에 code가 붙게 되는데, 이것이 인가 코드입니다.
인가 코드를 정상적으로 받았다면, 이걸 불러와서 다시 POST 요청으로 토큰을 받아와야 합니다!
인가 코드로 토큰 발급을 요청합니다.
인가 코드 받기만으로는 카카오 로그인이 완료되지 않으며, 토큰 받기까지 마쳐야 카카오 로그인을 정상적으로 완료할 수 있습니다.
POST 요청을 보낼 때는 헤더에 Content-Type을 넣어서 보내야 하고,
grant_type, client_id, redirect_uri, code를 포함해서 보내야 합니다.
// login.js
window.onload = () => {
const url = new URL(location.href);
const urlParams = url.searchParams
const authorizationCode = urlParams.get('code');
if (authorizationCode) {
axios.post('http://localhost:3000/kakao/login', {
authorizationCode
})
.then(res => {
kakaoAccessToken = res.data
return axios.post('http://localhost:3000/kakao/userinfo', { kakaoAccessToken })
})
.then(res => {
renderUserInfo(res.data.profile_image, res.data.nickname)
currentOAuthService = 'kakao'
})
}
}
};
먼저 url에 쿼리 파라미터로 포함되어 있던 인가 코드를 가져옵니다.
const url = new URL(location.href);
브라우저의 현재 페이지의 URL(전체 주소)을 가져와서 URL 객체를 생성합니다.
const urlParams = url.searchParams
searchParams 객체는 URL의 쿼리 문자열(즉, ? 뒤에 나오는 부분)을 관리하는 객체입니다.
→ 이를 통해 code 값에 접근이 가능합니다.
const authorizationCode = urlParams.get('code');
쿼리 파라미터 중 code라는 키의 값을 가져와서 authorizationCode라는 변수에 할당합니다.
(만약 code 파라미터가 없다면 null 반환)
authorizationCode가 존재한다면 axios.post를 통해 서버에 POST 요청을 보냅니다.
authorizationCode도 함께 전달해야겠죠.
// server.js
app.post('/kakao/login', (req, res) => {
const authorizationCode = req.body.authorizationCode
axios.post('https://kauth.kakao.com/oauth/token', {
grant_type: 'authorization_code',
client_id: kakaoClientId,
redirect_uri: redirectURI,
code: authorizationCode
},
{
headers: {'Content-type': 'application/x-www-form-urlencoded;charset=utf-8'}
}
)
.then(response => res.send(response.data.access_token))
})
서버에서 POST 요청이 들어오게 되면,
req.body(요청 바디)에서 authorizationCode를 우선 받아옵니다.
그리고 카카오 디벨로퍼의 문서에 따라 grant_type, client_id, redirect_uri, code와 헤더를 함께 담아 POST 요청을 보냅니다.
정상적으로 요청이 성공했을 경우, 응답으로 access token을 받아올 수 있습니다.
(req, res와 다른 변수명이 겹쳐선 안돼서 response 변수명으로 받아왔습니다.)
응답을 그대로 받아오면 어떻게 생겼냐면
...
...
},
[Symbol(errored)]: null,
[Symbol(kHighWaterMark)]: 16384,
[Symbol(kRejectNonStandardBodyWrites)]: false,
[Symbol(kUniqueHeaders)]: null
},
data: {
access_token: 'Ns37VpCumj9GtJ03jkPfLndye9e7SVBEAAAAAQo8I-kAAAGTT9wFdlR13198v8Zc',
token_type: 'bearer',
refresh_token: 'CdxV3H_jZ5sIlhvyX84RUiVAUQ894VLZAAAAAgo8I-kAAAGTT9wFc1R13198v8Zc',
expires_in: 21599,
scope: 'profile_image profile_nickname',
refresh_token_expires_in: 5183999
}
이렇게 생겼습니다.
data 안에 access_token 보이시죠. 저걸 받아오고 응답으로 다시 클라이언트에 보내주면 됩니다!
.then(response => res.send(response.data.access_token))
// login.js
.then(res => {
kakaoAccessToken = res.data
return axios.post('http://localhost:3000/kakao/userinfo', { kakaoAccessToken })
})
그럼 이 응답을 받아와서, 액세스 토큰을 변수에 저장해줍니다.
그리고 이제 이 토큰을 이용해서 정보 요청을 해주면 됩니다.
axios.post를 통해 서버에 POST 요청을 해줍니다.
4. 사용자 정보 요청
필수로 포함해야 하는 쿼리 파라미터는 없고, 헤더만 적어서 보내면 됩니다.
// server.js
app.post('/kakao/userinfo', (req, res) => {
const { kakaoAccessToken } = req.body
axios.get('https://kapi.kakao.com/v2/user/me', {
headers: {
Authorization: `Bearer ${kakaoAccessToken}`,
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
}
})
.then(response => res.json(response.data.properties))
})
액세스 토큰을 요청에 함께 보냈으니 똑같이 req.body로 액세스 토큰을 받아옵니다.
그리고 header에 내용을 담아서 GET 요청을 보내줍니다!
응답이 어떻게 오냐면...
{
status: 200,
statusText: 'OK',
headers: Object [AxiosHeaders] {
date: 'Thu, 21 Nov 2024 18:02:56 GMT',
server: 'Apache',
...
...
[Symbol(kUniqueHeaders)]: null
},
data: {
id: 3798701091,
connected_at: '2024-11-19T04:18:17Z',
properties: {
nickname: 'xu',
profile_image: '/img_640x640.jpg',
thumbnail_image: '/img_110x110.jpg'
},
kakao_account: {
profile_nickname_needs_agreement: false,
profile_image_needs_agreement: false,
profile: [Object]
}
}
}
이렇게 옵니다.
마찬가지로 data 안에 properties에 제가 원하는 정보가 들어있는 것을 볼 수 있습니다.
.then(response => res.json(response.data.properties))
then 체이닝으로 받아온 후, 응답으로 보내줍니다.
// login.js
.then(res => {
kakaoAccessToken = res.data
return axios.post('http://localhost:3000/kakao/userinfo', { kakaoAccessToken })
})
.then(res => {
renderUserInfo(res.data.profile_image, res.data.nickname)
currentOAuthService = 'kakao'
})
해당 응답을 받아서 renderUserInfo에 프로필 이미지랑 닉네임을 전달해줍니다.
function renderUserInfo(imgUrl, name) {
userImage.src = imgUrl
userName.textContent = name
}
renderUserInfo는 이렇게 생겼습니다.
이렇게 되면 정상적으로 로그인이 성공했을 경우, 프로필 이미지와 이름이 화면에 렌더링 되게 됩니다.
로그아웃은 POST 요청을 보내면 되는데, 헤더에 액세스 토큰만 포함해서 보내면 되네요.
// login.js
logoutButton.onclick = () => {
axios.delete('http://localhost:3000/kakao/logout', {
data: { kakaoAccessToken }
})
.then(res => {
console.log(res.data)
renderUserInfo('', '')
})
}
서버에 DELETE 요청을 보냅니다!
그리고 data에 액세스 토큰을 담아서 보내줍니다.
// server.js
app.delete('/kakao/logout', (req, res) =>{
const {kakaoAccessToken} = req.body
axios.post('https://kapi.kakao.com/v1/user/logout', {}, {
headers: {
Authorization: `Bearer ${kakaoAccessToken}`
}
})
.then(response => res.send('로그아웃 성공'))
})
req.body로 액세스 토큰을 받아온 다음, POST 요청을 보내줍니다.
응답으로는 '로그아웃 성공'이라는 텍스트를 보내줍니다.
그리고 클라이언트에서 renderInfo 함수에 빈 값들을 담아 다시 호출해주면 렌더링됐던 정보들도 사라지게 됩니다!
네이버 로그인도 구현했었는데, 자꾸 시크릿 키랑 아이디랑 안 맞는다고 떠서...분명히 제대로 적었는데...ㅠㅠ
그래서 일단 카카오만 기록합니다 흑흑
'개발 > Node.js' 카테고리의 다른 글
[Node.js] OAuth 2.0 (0) | 2024.11.20 |
---|---|
[Node.js] CORS와 SOP의 개념 및 구현 방법 (0) | 2024.11.13 |
[Node.js] HTTP와 HTTPS의 차이점 및 보안 메커니즘 (1) | 2024.11.13 |
[Node.js] 네트워크의 기본 구조 (1) | 2024.11.13 |