3-4. 로컬인증을 위한 API 준비하기
API 컨트롤러 및 라우트 초기설정
회원 인증 api 의 라우트는 /api/auth
로 사용하도록 하겠습니다. 이에 맞춰 디렉토리에 auth 디렉토리를 만들고, 라우트에서 필요한 파일들을 생성하세요.
src/api/auth/auth.controller.js
const Joi = require('joi');
const Account = require('models/Account');
// 로컬 회원가입
exports.localRegister = async (ctx) => {
ctx.body = 'register';
};
// 로컬 로그인
exports.localLogin = async (ctx) => {
ctx.body = 'login';
};
// 이메일 / 아이디 존재유무 확인
exports.exists = async (ctx) => {
ctx.body = 'exists';
};
// 로그아웃
exports.logout = async (ctx) => {
ctx.body = 'logout';
};
먼저, 추후 구현할 컨트롤러 함수들을 미리 준비하였습니다. 잠시 후 API 테스팅을 해봐야 하므로, 임시적으로 문자열을 보여주도록 설정을 했습니다. 그리고, 추후 필요해질 라이브러리 joi 와, Account 모델도 미리 불러왔습니다.
src/api/auth/index.js
const Router = require('koa-router');
const auth = new Router();
const authCtrl = require('./auth.controller');
auth.post('/register/local', authCtrl.localRegister);
auth.post('/login/local', authCtrl.localLogin);
auth.get('/exists/:key(email|username)/:value', authCtrl.exists);
auth.post('/logout', authCtrl.logout);
module.exports = auth;
3번째 API exists 에서는 :key(email|username)
이 사용되었는데, 이 의미는 key 라는 파라미터를 설정하는데, 이 값들이 email 이나 username 일때만 허용한다는 것 입니다.
src/api/index.js
const Router = require('koa-router');
const api = new Router();
const auth = require('./auth');
api.use('/auth', auth.routes());
module.exports = api;
이렇게 파일들의 초기설정을 하고 난 다음엔 아래 주소들을 테스트해보세요.
- POST http://localhost:4000/api/auth/register/local
- POST http://localhost:4000/api/auth/login/local
- GET http://localhost:4000/api/auth/exists/email/tester@test.com
- GET http://localhost:4000/api/auth/exists/username/tester
- POST http://localhost:4000/api/auth/logout
이메일 회원가입 구현하기
이메일 회원가입에선, 아이디 존재유무, 이메일 존재유무를 판단해야합니다. 하지만, 일단 계정정보들이 존재해야 이런 작업들도 테스팅을 할 수 있기 때문에 먼저 데이터를 생성하는 부분만 구현을 해보도록 하겠습니다.
이 API 의 주소는 /api/auth/register/local
이며 POST 요청정보는 다음 값들을 받게 됩니다:
{
username,
email,
password
}
먼저 이 요청정보를 검증하는 코드를 작성할것인데요, 아이디는 4~15자의 영소문자이며, 비밀번호는 최소 6자, 이메일은, 당연하게도, 이메일 형식이어야 합니다.
Joi 를 사용하여 검증하는 코드를 작성해보겠습니다.
src/api/auth/auth.controller.js
- localRegister
exports.localRegister = async (ctx) => {
// 데이터 검증
const schema = Joi.object().keys({
username: Joi.string().alphanum().min(4).max(15).required(),
email: Joi.string().email().required(),
password: Joi.string().required().min(6)
});
const result = Joi.validate(ctx.request.body, schema);
// 스키마 검증 실패
if(result.error) {
ctx.status = 400;
return;
}
};
그 다음 작업은 이메일 / 유저네임 중복확인 작업이지만 이는 주석처리를 하고 데이터를 한번 생성해본다음에 구현하도록 하겠습니다.
그럼, 곧 바로 데이터를 생성하겠습니다. 우리가 아까 만들었던, Account 모델의 localRegister 함수의 파라미터로, 객체가 들어가도록 설정했었지요? 이 객체의 스키마가 POST 요청정보와 동일합니다.
따라서, 다음과 같이 바로 함수에 전달을 해주면 되겠습니다.
src/api/auth/auth.controller.js
- localRegister
// 로컬 회원가입
exports.localRegister = async (ctx) => {
// 데이터 검증
const schema = Joi.object().keys({
username: Joi.string().alphanum().min(4).max(15).required(),
email: Joi.string().email().required(),
password: Joi.string().required().min(6)
});
const result = Joi.validate(ctx.request.body, schema);
if(result.error) {
ctx.status = 400;
return;
}
/* TODO: 아이디 / 이메일 중복처리 구현 */
// 계정 생성
let account = null;
try {
account = await Account.localRegister(ctx.request.body);
} catch (e) {
ctx.throw(500, e);
}
ctx.body = account.profile; // 프로필 정보로 응답합니다.
};
코드를 다 작성하셨다면, 다음 요청을 테스트해보세요.
요청
POST http://localhost:4000/api/auth/register/local
{
"username": "velopert",
"email": "public.velopert@gmail.com",
"password": "123123"
}
응답
{
"thumbnail": "/static/images/default_thumbnail.png",
"username": "velopert"
}
이렇게 되었다면, 계정생성이 성공한것입니다.
이메일 회원가입 - 중복 아이디/이메일 확인하기
이제 중복되는 아이디/이메일이 있는지 검증을 해보겠습니다. 이때 사용되는 함수는 Account.findByEmailOrUsername 입니다. 데이터 찾기를 시도하고, 만약에 존재하는 데이터가 있으면 409 에러코드로 응답하고, 어떤 키가 중복하는지 알려주도록 설정하겠습니다.
src/api/auth/auth.controller.js
- localRegister
// 로컬 회원가입
exports.localRegister = async (ctx) => {
// 데이터 검증
const schema = Joi.object().keys({
username: Joi.string().alphanum().min(4).max(15).required(),
email: Joi.string().email().required(),
password: Joi.string().required().min(6)
});
const result = Joi.validate(ctx.request.body, schema);
if(result.error) {
ctx.status = 400; // Bad request
return;
}
// 아이디 / 이메일 중복 체크
let existing = null;
try {
existing = await Account.findByEmailOrUsername(ctx.request.body);
} catch (e) {
ctx.throw(500, e);
}
if(existing) {
// 중복되는 아이디/이메일이 있을 경우
ctx.status = 409; // Conflict
// 어떤 값이 중복되었는지 알려줍니다
ctx.body = {
key: existing.email === ctx.request.body.email ? 'email' : 'username'
};
return;
}
// 계정 생성
let account = null;
try {
account = await Account.localRegister(ctx.request.body);
} catch (e) {
ctx.throw(500, e);
}
ctx.body = account.profile; // 프로필 정보로 응답합니다.
};
이제 아까와 똑같은 정보로 요청을 넣어보세요. 에러가 뜨나요? 그렇다면 username 을 조금 바꾸고 요청을 해보고, 에러가 뜬다면 성공입니다! 그 다음엔 또 다시 이메일을 조금 바꾸고 요청을 다시 해보세요.
이메일 로그인 구현하기
이메일 로그인은 회원가입보단 조금 더 쉽습니다.
먼저, POST 요청 정보부터 검증을 해보도록 하겠습니다.
src/api/auth/auth.controller.js
- localLogin
exports.localLogin = async (ctx) => {
// 데이터 검증
const schema = Joi.object().keys({
email: Joi.string().email().required(),
password: Joi.string().required()
});
const result = Joi.validate(ctx.request.body, schema);
if(result.error) {
ctx.status = 400; // Bad Request
return;
}
};
그 다음에는, 우선 findByEmail 을 통하여 게정을 찾고, 만약에 데이터가 없다면 에러를 응답하고, 존재한다면 비밀번호체크 과정을 수행합니다.
src/api/auth/auth.controller.js
- localLogin
exports.localLogin = async (ctx) => {
// 데이터 검증
const schema = Joi.object().keys({
email: Joi.string().email().required(),
password: Joi.string().required()
});
const result = Joi.validate(ctx.request.body, schema);
if(result.error) {
ctx.status = 400; // Bad Request
return;
}
const { email, password } = ctx.request.body;
let account = null;
try {
// 이메일로 계정 찾기
account = await Account.findByEmail(email);
} catch (e) {
ctx.throw(500, e);
}
if(!account || !account.validatePassword(password)) {
// 유저가 존재하지 않거나 || 비밀번호가 일치하지 않으면
ctx.status = 403; // Forbidden
return;
}
ctx.body = account.profile;
};
이제, 아까 만들었던 계정정보로 로그인 요청을 해보세요. 프로필이 제대로 응답되었다면, 잘못된 계정으로도 로그인을 시도해보세요.
요청
POST http://localhost:4000/api/auth/login/local
{
"email": "public.velopert@gmail.com",
"password": "123123"
}
응답
{
"thumbnail": "/static/images/default_thumbnail.png",
"username": "velopert"
}
아이디/이메일 중복확인 API 만들기
회원가입 API를 문제없이 만들었다면, 이 API는 쉽게 만들 수 있을 것 입니다.
src/api/auth/auth.controller.js
- exists
exports.exists = async (ctx) => {
const { key, value } = ctx.params;
let account = null;
try {
// key 에 따라 findByEmail 혹은 findByUsername 을 실행합니다.
account = await (key === 'email' ? Account.findByEmail(value) : Account.findByUsername(value));
} catch (e) {
ctx.throw(500, e);
}
ctx.body = {
exists: account !== null
};
};
위 코드에서 주의 하실 점은, await 뒤에 ()
로 감싸주어야 한 다는 것 입니다. 그렇게 하지 않으면 코드가 이상작동을 하게 됩니다.
API 를 작성하셨다면, 다음과 같이 테스트를 해보세요.
GET http://localhost:4000/api/auth/exists/email/public.velopert@gmail.com
GET http://localhost:4000/api/auth/exists/email/new.public.velopert@gmail.com
GET http://localhost:4000/api/auth/exists/username/velopert
GET http://localhost:4000/api/auth/exists/username/newvelopert
이제, 회원 인증을 위한 API 를 어느정도 준비했습니다. 사용자가 로그인을 했을 땐, 사용자가 로그인을 했다는것을 인지하기 위하여 인증정보를 관리 할 필요가 있습니다. 이에 대해선 다음 이어지는 4장에서 다루도록 하겠습니다.