지갑 서명으로 로그인시키기 — SIWE와 SSI로 짜는 Web3 인증 구조
EIP-4361(SIWE) 서명 기반 인증과 DID·VC 기반 SSI를 결합한 Web3 DApp 인증 아키텍처와 구현·운영 시 주의점을 정리한다.
2026-08-12 · 최초 발행 2025-12-12
패스워드를 서버 어딘가에 저장하지 않고도 로그인을 시키는 방법은 이미 여러 서비스에서 검증됐다. Web3 환경에서는 그 역할을 지갑 서명이 대신한다. EIP-4361(SIWE: Sign-In with Ethereum)로 대표되는 Web3 Authentication과, DID·VC로 신원을 증명하는 SSI(Self-Sovereign Identity)는 서로 다른 문제를 풀지만 결합하면 DApp의 인증·인가 계층 전체를 커버할 수 있다.
개념이 맞물리는 지점
DApp은 스마트 컨트랙트를 백엔드로, 프런트엔드는 일반 웹·모바일로 구성되는 애플리케이션이다. 데이터 일부는 온체인에, 나머지는 오프체인 인덱서나 분산 저장소(IPFS/Arweave)에 흩어져 있다.
Web3 Authentication은 비대칭키 서명 기반 로그인 패턴이다. EIP-4361 메시지 서명으로 도메인 바인딩, 체인ID 검증, 리플레이 방지(Nonce)를 처리하며, OIDC 브리지를 두면 기존 SSO와도 상호운용할 수 있다.
SSI는 DID(Decentralized Identifier)·VC(Verifiable Credential)·VP(Verifiable Presentation) 세 요소로 구성된 탈중앙 신원 체계다. 발행자(Issuer)-홀더(Holder)-검증자(Verifier) 모델과 트러스트 레지스트리·리보케이션 메커니즘으로 위조·폐기 상태를 검증한다.
계층 구조에서 서명·검증 경계를 어디에 두나
프런트엔드·지갑·RPC 노드·스마트 컨트랙트·오라클·분산 스토리지로 계층화하고, 각 계층 사이의 서명·검증 경계를 명확히 정의하는 게 출발점이다. The Graph 같은 인덱서와 캐시 계층을 두면 조회 성능을 확보할 수 있고, 이벤트 소싱을 쓰면 상태 복원 가능성도 함께 얻는다.
데이터 배치는 온체인·오프체인으로 나뉜다. 온체인은 불변 로그와 합의·최종성(finality)을 보장하고, 오프체인은 대용량·비정형 데이터를 저지연으로 처리한다. L2 Rollup과 상태 커밋(DA 레이어)을 쓸 때는 리오그(reorg)와 최종성 지연을 고려한 읽기 일관성 전략이 필요하다.
인증·식별 메커니즘에서 Web3 Auth는 EIP-4361 메시지 서명과 Nonce·만료·도메인 바인딩, 체인ID 확인으로 재사용과 피싱을 막고, 서버는 서명 검증 후 세션·JWT를 발급한다. SSI는 DID 해석과 VC 서명·발행자 신뢰 검증, VP의 선택적 공개를 거쳐 리보케이션·만료·정책 평가까지 포함한다.
키 관리는 하드웨어 지갑, MPC, 소셜 리커버리로 유실 리스크를 완화하고 스마트 계정(Account Abstraction)으로 정책적 제어를 얹는다. 컨트랙트 보안은 포멀 검증·감사와 업그레이드 프록시의 트러스트 어슈어런스, Role/Permit2 같은 권한 제어가 뒤따른다. 운영 측면에서는 RPC 멀티엔드포인트와 자동 페일오버, 캐시·레이트리밋·백오프 재시도, 블록 최종성 대기·트랜잭션 실패율·가스 비용 모니터링으로 SLA를 관리한다.
인증 흐름
인증 처리 절차
입력은 사용자의 지갑 주소, EIP-4361 메시지(도메인·주소·체인ID·Nonce·만료), 서명값이다. SSI를 쓰는 경우 VP와 정책 요구사항이 추가된다.
처리는 네 단계로 진행된다. 먼저 Nonce를 발급·저장(단일 사용, TTL 지정)하고 메시지 구성(도메인 바인딩·체인ID 일치·만료)을 검증한다. 서명 검증 후에는 Nonce 사용 플래그를 원자적 트랜잭션으로 업데이트해 리플레이를 방지한다. 성공하면 세션·JWT를 발급(claim: address, chainId, iat, exp, amr='swk')하고, SSI를 쓸 경우 VP를 검증(서명·발행자 신뢰·리보케이션·만료)한다. 마지막으로 온체인 역할 조회나 VC 클레임 매핑으로 권한을 결정한다.
출력은 성공 시 JWT·세션 토큰과 권한 컨텍스트이고, 실패 시 오류 코드(400 메시지 형식 오류, 401 서명 불일치, 409 Nonce 재사용, 422 VP 검증 실패)로 나뉜다.
트랜잭션·일관성 측면에서는 Nonce 레코드의 상태 전이(new→used)를 단일 트랜잭션으로 처리하고, 고유 인덱스와 상태 체크로 중복 요청을 막는다. 세션 발급은 idempotency 키를 적용하고, 블록 최종성 대기(예: L2 1~5블록) 후 권한 조회 결과를 캐시 TTL로 관리한다.
어디에 쓰이나
기업 포털의 Web3 SSO 브리지는 기존 OIDC 공급자에 Web3 Auth 어댑터를 추가해, 지갑 서명으로 1차 인증한 뒤 OIDC 토큰으로 사내 리소스에 접근하게 한다. 패스워드 없이 서명 기반 인증으로 전환하면 평균 로그인 시간을 3050% 단축할 수 있고, 비밀번호 재설정 티켓도 70% 이상 감소하는 것으로 가정된다. 중앙 인증 스토리지를 축소하면 보안·규정 준수 비용을 2040% 절감할 수 있으며 피싱·크리덴셜 스터핑 리스크도 크게 줄어든다(다만 이 수치는 조직·트래픽·체인 특성에 따라 변동한다). 규제 친화형 온보딩(KYC)은 신뢰된 발행자가 발급한 KYC VC를 보관해두고 VP 검증만으로 최소 정보 기반 온보딩을 처리한다. 공급망 추적은 업체별 VC로 품질·원산지를 증명하고, 검증자는 VP 확인과 온체인 앵커 해시 대조로 위변조를 막는다. 토큰 게이팅·역할 기반 접근은 NFT·토큰 보유나 온체인 역할(AccessControl), VC 클레임을 권한에 매핑하고 캐시와 이벤트 구독으로 실시간 반영한다.
Web2 중앙형 vs Web3 탈중앙형
| 지표 | Web2 중앙형 | Web3 탈중앙형 |
|---|---|---|
| 성능(지연) | 낮은 지연, 수 ms~수십 ms | 체인·최종성 영향, 수십 ms~수초 |
| 확장성 | 수평 확장 용이 | 읽기 확장 우수, 쓰기는 합의 한계 |
| 일관성 | 강한 일관성 가능 | 확률적 최종성, 리오그 고려 필요 |
| 안정성/내결함성 | 영역 장애에 취약 | 네트워크 레벨 내결함성 우수 |
| 운영 편의 | 도구 성숙, 표준 풍부 | 도구 성숙도 상이, 운영 복합성 증가 |
SIWE 최소 구현
전제는 Node.js 18+, Next.js 14, siwe·jose 패키지, MetaMask 등 EIP-1193 provider다. 서버는 /api/siwe/nonce와 /api/siwe/verify 두 엔드포인트를 둔다.
// /pages/api/siwe/nonce.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const nonce = crypto.randomUUID().replace(/-/g, '').slice(0, 16);
// TODO: nonce를 저장소에 upsert(status='new', ttl=5분)
res.status(200).json({ nonce });
}
// /pages/api/siwe/verify.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { SiweMessage } from 'siwe';
import { SignJWT } from 'jose';
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET || 'change-me');
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { message, signature } = req.body as { message: string; signature: string };
const siwe = new SiweMessage(message);
const fields = await siwe.verify({ signature, domain: req.headers.host ?? '', time: new Date().toISOString() });
// TODO:
// 1) 저장소에서 nonce='new' 검증 후 'used'로 원자적 업데이트(트랜잭션)
// 2) chainId, statement 등 정책 점검
const token = await new SignJWT({ sub: fields.data.address, amr: ['swk'], chainId: fields.data.chainId })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('30m')
.sign(JWT_SECRET);
res.status(200).json({ token });
} catch (e) {
res.status(401).json({ error: 'invalid_signature_or_message' });
}
}
프런트엔드는 지갑에 EIP-4361 메시지 서명을 요청한다.
// 간단 예시: window.ethereum 사용
import { SiweMessage } from 'siwe';
async function siweLogin() {
const [address] = await window.ethereum.request({ method: 'eth_requestAccounts' });
const nonceRes = await fetch('/api/siwe/nonce');
const { nonce } = await nonceRes.json();
const msg = new SiweMessage({
domain: window.location.host,
address,
statement: 'Sign in with Ethereum',
uri: window.location.origin,
version: '1',
chainId: 1,
nonce,
});
const message = msg.prepareMessage();
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, address],
});
const verifyRes = await fetch('/api/siwe/verify', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ message, signature }),
});
if (!verifyRes.ok) throw new Error('Login failed');
const { token } = await verifyRes.json();
// token 저장 및 후속 호출에 Bearer 사용
}
Nonce는 단일 사용·TTL을 지키고, 체인ID 검증과 메시지 만료 설정을 빠뜨리지 않아야 한다. JWT는 짧은 만료와 리프레시 전략을 쓴다. EIP-4361과 OpenID for Verifiable Presentations(SIOP v2) 같은 표준은 변동 가능성이 있으므로 최신 정보를 확인해야 한다.
모범사례와 트레이드오프
보안 측면에서는 하드웨어 지갑을 우선하고 피싱 방지 문구를 고정하며, Permit은 최소 권한·만료로 제한한다. 업그레이더블 컨트랙트는 제한적 관리자 권한과 타임락을 함께 적용한다. 성능은 읽기 캐시·서머리 테이블·인덱서로 확보하고, 쓰기 경로는 배치·멀티콜·가스 최적화와 함께 최종성 대기 중 사용자 피드백을 제공하는 게 중요하다. 운영은 RPC 멀티 공급자와 자동 페일오버, 지연·오류율 모니터링, 이벤트 기반 재처리와 데드레터 큐로 뒷받침한다. 프라이버시는 SSI의 선택적 공개·영지식증명을 활용하고 오프체인 민감 데이터는 암호화 저장한다.
브리지·게이팅·SSI KYC 순서로 점진 도입하고 운영을 자동화하면 리스크를 통제하면서 확장할 수 있다.