ⓐ 자격증명 로그인 API
쏠브가 회원의 ID/PW 를 보내면 확인하고 user_key 를 돌려주는, 제휴사가 구현하는 API 입니다.
회원이 쏠브 앱에 입력한 ID/PW 를 쏠브 서버가 그대로 보내면, 제휴사가 확인한 뒤 user_key 를 돌려주는 server-to-server API 입니다. 자체 ID/PW 로그인만 운영하는 제휴사에 권장합니다. 소셜 로그인을 함께 운영한다면 ⓑ 호스팅 로그인 페이지를 쓰세요.
한눈에 보기
| 항목 | 내용 |
|---|---|
| 호출하는 쪽 | 쏠브 서버 → 제휴사 서버 |
| 메서드·URL | POST, URL 은 제휴사가 정합니다 |
| 형식 | Content-Type: application/json 요청, JSON 응답 |
| 인증 | 제휴사가 발급한 시크릿을 쏠브가 헤더에 담습니다. 헤더 이름과 방식은 제휴사가 정합니다. |
| 타임아웃 | 호출당 10초 |
| 재시도 | 5xx·타임아웃이면 최대 2번 더 호출합니다. 4xx 는 재시도하지 않습니다. |
요청
쏠브가 보내는 요청은 아래와 같습니다. 헤더 이름 X-Solve-Secret 은 예시입니다.
curl -X POST https://api.partner.example.com/solve/login \
-H 'Content-Type: application/json' \
-H 'X-Solve-Secret: <제휴사가 발급한 시크릿>' \
-d '{"id": "partner_user_01", "password": "p@ssw0rd"}'| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
id | string | 필수 | 회원이 입력한 로그인 아이디 |
password | string | 필수 | 회원이 입력한 비밀번호 |
응답
성공 (200)
{ "user_key": "100023" }| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
user_key | string | 필수 | 회원의 불변·유일 식별자, 최대 255자. 내부 회원번호를 권장합니다. 다른 필드를 더 담아도 쏠브는 무시합니다. |
응답 본문은 1MB(1,048,576바이트) 이하여야 합니다. 쏠브는 1MB 를 넘는 응답을 받지 않습니다. 넘으면 그 로그인 호출은 실패로 처리하고 재시도하지 않습니다. 압축해서 보내는 경우는 푼 뒤의 크기로 셉니다.
실패
쏠브는 상태 코드로 인증 실패와 시스템 오류를 구분해 회원에게 다른 안내를 보여줍니다. 본문 형식은 자유입니다.
| 상황 | 상태 코드 | 쏠브 앱에서 보이는 안내 | 재시도 |
|---|---|---|---|
| 아이디·비밀번호 불일치 | 401 등 4xx | "아이디 또는 비밀번호가 일치하지 않습니다" | 안 함 |
| 시크릿 불일치 | 401 또는 403 | 위와 같음 | 안 함 |
| 요청 형식 오류 | 400 등 4xx | 위와 같음 | 안 함 |
| 서버 오류, DB 장애 | 500 등 5xx | 일반 오류 안내 | 최대 2번 |
쏠브는 401 과 403 을 구분하지 않고 같은 인증 실패로 처리합니다. 둘 다 재시도하지 않습니다.
서버 오류를 4xx 로 보내지 마세요
장애 상황을 4xx 로 응답하면 회원은 비밀번호가 틀린 줄 알고 계속 다시 입력합니다. 예외가 나면 반드시 5xx 로 응답해 주세요.
구현 예시
시크릿 확인 → 입력값 확인 → 회원 인증 → user_key 응답 순서입니다. 회원 조회·비밀번호 검증 함수는 제휴사 시스템에 맞게 바꾸세요.
import express from 'express';
import crypto from 'node:crypto';
const app = express();
app.use(express.json());
// 제휴사가 발급해 쏠브에 전달한 시크릿
const SOLVE_SECRET = Buffer.from(process.env.SOLVE_SECRET ?? '');
// 시크릿이 비어 있으면 빈 헤더 요청이 통과하므로 서버를 띄우지 않는다
if (SOLVE_SECRET.length === 0) throw new Error('SOLVE_SECRET is not set');
function isFromSolve(req) {
const given = Buffer.from(req.get('X-Solve-Secret') ?? '');
return given.length === SOLVE_SECRET.length && crypto.timingSafeEqual(given, SOLVE_SECRET);
}
app.post('/solve/login', async (req, res) => {
if (!isFromSolve(req)) return res.status(403).json({ error: 'forbidden' });
const { id, password } = req.body ?? {};
if (typeof id !== 'string' || typeof password !== 'string') {
return res.status(400).json({ error: 'invalid_request' });
}
try {
const member = await findMemberByLoginId(id);
if (!member || !(await verifyPassword(password, member.passwordHash))) {
return res.status(401).json({ error: 'invalid_credentials' });
}
// 로그인 아이디가 아니라 절대 바뀌지 않는 내부 회원번호
return res.json({ user_key: String(member.memberNo) });
} catch (err) {
console.error('solve login failed', err);
return res.status(500).json({ error: 'internal_error' });
}
});흐름
회원
쏠브 앱
제휴사
사이트·서버
쏠브
서버
회원이 제휴사 사이트에서 책을 삽니다. 여기까지는 지금과 같습니다.
- 회원 → 제휴사: 책 구매. 회원이 제휴사 사이트에서 책을 삽니다. 여기까지는 지금과 같습니다.
- 회원 → 쏠브: 아이디·비밀번호 입력. 쏠브 앱에서 "제휴사 연동하기"를 누르고 제휴사 아이디와 비밀번호를 입력합니다.
- 쏠브 → 제휴사: "이 아이디 맞나요?". 쏠브 서버가 제휴사에 아이디와 비밀번호가 맞는지 물어봅니다.
- 제휴사 → 쏠브: "100023번 회원이에요". 맞으면 제휴사가 회원 번호로 답합니다. 틀리면 "비밀번호 불일치"로 답합니다.
- 쏠브 → 제휴사: "이 회원 뭐 샀어요?". 쏠브가 제휴사에 이 회원이 지금 볼 수 있는 상품을 물어봅니다.
- 제휴사 → 쏠브: 상품코드 목록. 제휴사는 구매한 상품의 코드 목록으로 답합니다. 환불된 상품은 빼고 보냅니다.
- 쏠브 → 회원: 책장에 책 추가. 쏠브가 상품코드로 전자책을 찾아 회원의 책장에 넣습니다. 상품코드는 파트너 어드민에 미리 등록해 둡니다.
구현 체크리스트
- 로그인에 성공하면 내부 회원번호처럼 바뀌지 않는 값을
user_key로 돌려준다 - 비밀번호 불일치는
4xx, 서버 오류는5xx로 응답한다 - 응답 본문을 1MB(1,048,576바이트) 이하로 만든다
- 시크릿을 상수 시간 비교(
timingSafeEqual,hash_equals등)로 확인한다 - ID/PW 를 로그에 남기지 않는다
- 같은 요청을 여러 번 받아도 데이터가 바뀌지 않는다 (쏠브가 재시도할 수 있음)
체크리스트 항목 중 응답으로 확인할 수 있는 것(맞는·틀린 비밀번호, 틀린 시크릿, user_key 형식, 응답 시간)은 전체 점검 도구로 한 번에 점검할 수 있습니다.