반응형

SIWE (Sign In With Ethereum)


SIWE 는 이더리움 블록체인 기반의 사용자 인증 방식. 사용자는 자신의 이더리움 지갑을 통해 웹 서비스나 앱에 로그인할 수 있으며, 이는 기존의 중앙화된 아이디 제공자 (IDP) 대신 자신이 소유한 암호화폐 지갑으로 신원을 증명하는 방식이다.

최근에 회사의 Dapp 에 SIWE 인증 서비스를 도입했다. SIWE 를 도입하면서 백엔드 개발자 분들과 약간의 논의가 있었는데 ‘인증 안해도 로그인 되는데 굳이 로그인을 해야하나?’ 라는 이유였다. 나는 여기서 제대로 답을 하지 못해 리서치 겸 글을 작성해본다.

Web2 에서의 인증


1. 비밀번호 기반 인증 (ID/PW)

  • 사용자가 아이디와 비밀번호를 입력하면, 서버가 이를 데이터베이스와 대조하여 신원을 확인합니다.
  • 가장 기본적이고 널리 사용되는 방식이지만, 비밀번호 유출·재사용 등으로 인한 보안 취약점이 존재합니다.

2. 세션 기반 인증

  • 로그인 성공 시 서버가 세션을 생성하고, 세션 ID를 쿠키에 저장하여 클라이언트에 전달합니다.
  • 사용자가 요청할 때마다 쿠키에 담긴 세션 ID로 인증을 수행합니다.
  • 세션 정보는 서버에 저장되며, 서버 확장 시 세션 동기화가 필요합니다.

3. 토큰 기반 인증 (JWT 등)

  • 사용자가 로그인하면 서버가 JWT(JSON Web Token) 등 서명된 토큰을 발급합니다.
  • 클라이언트는 이후 요청에 이 토큰을 포함시켜 서버에 제출하며, 서버는 토큰의 유효성을 검증해 인증을 처리합니다.
  • 토큰은 클라이언트가 직접 저장·관리하며, 서버는 상태를 유지하지 않아 확장성이 높습니다.

4. OAuth 2.0 (소셜 로그인 등)

  • Google, Facebook, Kakao 등 외부 서비스 계정으로 로그인할 수 있게 해주는 표준 프로토콜입니다.
  • 대표적인 인증 방식:
    • Authorization Code Grant: 서버 사이드에서 인증, 보안성이 높아 가장 많이 사용됨
    • Implicit Grant: 클라이언트(브라우저)에서 직접 토큰을 받는 방식, 보안상 현재는 거의 사용하지 않음
    • Resource Owner Password Credentials Grant: 신뢰 관계가 있는 앱에서만 사용
    • Client Credentials Grant: 서버-서버 간 인증.
  • 사용자는 외부 서비스의 로그인 페이지에서 인증을 마치고, 서비스는 액세스 토큰을 받아 인증에 활용합니다.

5. 2단계 인증(2FA), 생체 인증, 인증서 기반 인증

  • 2단계 인증(2FA): 비밀번호 외에 일회용 코드(OTP), SMS, 이메일 인증 등 추가 인증을 요구.
  • 생체 인증: 지문, 얼굴 인식 등으로 인증.
  • 인증서 기반 인증: 공인인증서, 사설 인증서 등 디지털 인증서를 활용.

이처럼 Web2에서는 비밀번호, 세션, 토큰, OAuth, 2FA 등 다양한 인증 방식이 사용되며, 서비스의 특성과 보안 요구 수준에 따라 여러 방식을 조합해 적용하는 것이 일반적입니다.

Web3 에서의 SIWE 인증


  1. 라이브러리 혹은 지갑 연결
    • 라이브러리에서 제공하는 연결 혹은 injected 된 지갑을 통해 로그인
  2. SIWE 서명 프로세스 시작
  3. 서버에서 nonce , message, expired, 로그인 목적 등 생성 후 전송
  4. SIWE message 생성 후 사용자에게 출력
  5. 메시지 서명
  6. service.org wants you to sign in with your Ethereum account: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 I accept the ServiceOrg Terms of Service: https://service.org/tos URI: https://service.org/login Version: 1 Chain ID: 1 Nonce: 32891756 Issued At: 2021-09-30T16:25:24Z Resources: - ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ - https://example.com/my-web2-claim.json
  7. 서명 데이터 서버에 전달
  8. 서명 검증
  9. 인증 완료 및 세션 발급

 

이 프로세스만 봐서는 SIWE 를 써야하는 이유를 모를 수 있다. 쓰지 않았을 때의 문제점을 알아보자.

Why SIWE?


  1. 지갑 소유권 검증 부재
    • 단순히 지갑 주소 제출 및 연결하는 방식은 해당 주소의 실제 소유자를 검증 못함.
    • 누군가 공개된 이더리움 주소를 임의로 입력하거나, 다른 사용자의 주소를 도용해 서비스에 접근할 수 있음.
    • 서버에서는 사용자가 누구인지 확인할 방법이 없어, 주소만 알면 누구나 그 사용자인 척할 수 있는 심각한 보안 취약점.
  2. 세션 하이재킹 및 신원 도용
    • SIWE 는 서명 메시지에 nonce (임의의 난수) 와 세션 정보를 포함시켜, 재사용 공격이나 세션 하이재킹을 방지
      • 재사용 공격(Relay Attack)
        • 이미 생성된 서명을 재사용해 인증을 우회
      • 세션 하이재킹
        • nonce 관리가 제대로 이뤄지지 않아, 사용자가 로그아웃해도 동일한 서명 메시지로 재로그인이 가능
    • SIWE 없이 단순히 주소를 전달할 경우, 이전에 사용된 정보가 그대로 재사용 가능
  3. 백엔드 API 보호 미흡
    • 백엔드 API 호출 시 신원 검증이 어려움
    • 누군가가 임의의 주소로 API 를 호출하면, 서비스는 잘못 인식할 수 있음.

다른 방법은 없나?


SIWE 말고도 사용자의 신원을 파악하는 블록체인 기술은 여러가지가 있다.

  1. DID (분산신원 인증)
    • W3C DID 표준을 기반, 블록체인에 등록된 분산 신원(DID) 과 검증가능 자격증명 (VC) 를 활용해 자신을 증명
    • 모바일 운전면허증
  2. NFT/토큰 소유 기반 인증
    • 특정 NFT , 토큰을 소유한 지갑만 서비스 접근 가능
  3. 다중 인증 (MFA)
    • 지갑 인증 외에 이메일, SMS, 생체 인증 등 추가인증 결합
  4. 소셜 로그인 연동형 Web3 인증
    • 소셜 로그인 시 생기는 지갑을 연결 및 인증
    • Wepin, Appkit 소셜 로그인
반응형

'BlockChain > Web3' 카테고리의 다른 글

[2] Web3 프론트엔드 연결 요약 (Wagmi + Reown)  (0) 2025.04.12
[1] Frontend in Web3  (0) 2025.04.06
반응형

Web3 프론트엔드를 구성할 때 가장 중요한 요소 중 하나는 지갑 연결과 스마트 컨트랙트 연동의 안정성과 편의성입니다. 이번 글에서는 @reown/appkitwagmi, ethers, viem을 활용하여 모던한 Web3 dApp 환경을 구성하는 방법을 소개합니다.

특히, Reown에서 제공하는 AppKitButton을 사용해 지갑 연결 UI를 심플하게 구성하고, 스마트 컨트랙트 연동은 wagmi를 활용하여 안전하게 처리합니다.


🧱 1. 프로젝트 구조 개요

bash
복사편집
src/
├── appkit/                 # wagmi + appkit 설정
│   └── config.js
├── contracts/              # 스마트 컨트랙트 ABI 및 주소
│   ├── CounterABI.json
│   └── address.js
├── hooks/                  # wagmi용 커스텀 훅 (읽기/쓰기)
│   ├── useCounterRead.js
│   └── useCounterWrite.js
├── pages/
│   └── CounterPage.jsx     # 실제 스마트 컨트랙트 연동 화면
├── App.jsx                 # 전체 앱 엔트리 포인트

각 폴더 및 파일의 목적은 다음과 같습니다:

폴더/파일 설명
appkit/config.js AppKit + wagmi의 핵심 설정을 담당
contracts/CounterABI.json 스마트 컨트랙트 ABI 정의 파일
contracts/address.js 스마트 컨트랙트 주소 정의
hooks/ 컨트랙트 호출을 담당하는 useContractRead, useContractWrite 활용 커스텀 훅 모음
pages/CounterPage.jsx dApp 메인 화면. 읽기/쓰기 기능 제공
App.jsx AppKitProvider 및 WagmiConfig로 앱 전체를 감싸는 설정

⚙️ 2. AppKit + wagmi 설정

📁 파일: appkit/config.js

import { WagmiAdapter } from '@reown/appkit-adapter-wagmi';
import { mainnet, arbitrum, sepolia } from '@reown/appkit/networks';
import type { AppKitNetwork } from '@reown/appkit/networks';

export const projectId = import.meta.env.VITE_PROJECT_ID || "b56e18d47c72ab683b10814fe9495694"; // localhost 테스트용

export const metadata = {
  name: 'AppKit',
  description: 'AppKit Example',
  url: 'https://reown.com',
  icons: ['https://avatars.githubusercontent.com/u/179229932']
};

export const networks = [mainnet, arbitrum, sepolia] as [AppKitNetwork, ...AppKitNetwork[]];

export const wagmiAdapter = new WagmiAdapter({
  projectId,
  networks,
});

export const config = wagmiAdapter.wagmiConfig;

🧠 설명:

  • projectId: reown/appkit 기반 연결을 위한 ID입니다. Reown Cloud에서 발급.
  • WagmiAdapter: wagmi와 AppKit을 연결하는 설정자 역할.
  • wagmiConfig: 전체 앱에서 사용할 wagmi 설정을 반환합니다.

🧩 3. App.jsx 설정

📁 파일: App.jsx

import { WagmiConfig } from 'wagmi';
import { AppKitProvider } from '@reown/appkit';
import { wagmiAdapter, config, networks, metadata } from './appkit/config';
import CounterPage from './pages/CounterPage';

function App() {
  return (
    <WagmiConfig config={config}>
      <AppKitProvider adapter={wagmiAdapter} metadata={metadata}>
        <CounterPage />
      </AppKitProvider>
    </WagmiConfig>
  );
}

export default App;

🧠 설명:

WagmiConfigAppKitProvider로 전체 애플리케이션에 Web3 상태(Context)를 적용합니다.


🔐 4. 지갑 연결 버튼 - AppKitButton 사용

Reown은 UI 모달과 연결 기능이 포함된 AppKitButton을 제공합니다.

사용 예시 (CounterPage.jsx 내부에서 사용):


<AppKitButton />

🧠 설명:

AppKitButton은 내부적으로 지갑 연결 상태를 감지하고, 연결되지 않았다면 모달을 띄워 사용자가 지갑을 선택하게 유도합니다. 커스텀 버튼 대신 이 컴포넌트를 활용하면 통일성 있는 UI/UX 제공이 가능합니다.


🔍 5. 컨트랙트 읽기 훅 – useContractRead

📁 파일: hooks/useCounterRead.js


import { useContractRead } from 'wagmi';
import { COUNTER_ADDRESS } from '../contracts/address';
import abi from '../contracts/CounterABI.json';

export const useCounterRead = () => {
  const { data, refetch } = useContractRead({
    address: COUNTER_ADDRESS,
    abi,
    functionName: 'getCount',
    watch: true,
  });

  return { count: data?.toString() ?? '0', refetch };
};

🧠 설명:

스마트 컨트랙트의 getCount()를 호출하여 현재 카운터 값을 가져옵니다. watch: true 옵션으로 자동 업데이트도 가능하게 설정되어 있습니다.


✍️ 6. 컨트랙트 쓰기 훅 – useContractWrite

📁 파일: hooks/useCounterWrite.js


import { useContractWrite, useWaitForTransaction } from 'wagmi';
import { COUNTER_ADDRESS } from '../contracts/address';
import abi from '../contracts/CounterABI.json';

export const useCounterWrite = () => {
  const { data, write } = useContractWrite({
    address: COUNTER_ADDRESS,
    abi,
    functionName: 'increment',
  });

  const { isLoading, isSuccess } = useWaitForTransaction({ hash: data?.hash });

  return { write, isLoading, isSuccess };
};

🧠 설명:

  • increment() 함수 호출을 위해 트랜잭션을 생성합니다.
  • useWaitForTransaction() 훅으로 실제 블록체인에 기록될 때까지 대기합니다.

📄 7. 최종 화면 구성 – CounterPage.jsx

📁 파일: pages/CounterPage.jsx


import { useCounterRead } from '../hooks/useCounterRead';
import { useCounterWrite } from '../hooks/useCounterWrite';
import { useEffect } from 'react';

const CounterPage = () => {
  const { count, refetch } = useCounterRead();
  const { write, isLoading, isSuccess } = useCounterWrite();

  useEffect(() => {
    if (isSuccess) refetch();
  }, [isSuccess]);

  return (
    <div className="p-6">
      <AppKitButton />
      <h1 className="text-xl font-bold mt-4">현재 카운트: {count}</h1>
      <buttononClick={() => write?.()}
        disabled={isLoading}
        className="mt-4 px-4 py-2 bg-green-500 text-white rounded"
      >
        {isLoading ? '처리 중...' : '카운트 증가'}
      </button>
    </div>
  );
};

export default CounterPage;

🧠 설명:

  • 버튼 클릭 시 increment() 실행
  • 트랜잭션 완료 후 상태값 재조회
  • AppKitButton으로 지갑 UI 자동 처리

 

반응형

'BlockChain > Web3' 카테고리의 다른 글

[Blockchain] SIWE (Sign In With Ethereum)  (1) 2025.07.11
[1] Frontend in Web3  (0) 2025.04.06
반응형

🕸️ Web3에서 프론트엔드의 역할은 무엇일까?

Web3가 점차 주목받으면서 프론트엔드 개발자에게도 새로운 흐름이 요구되고 있습니다. 구조는 Web2와 크게 다르지 않지만, 기술적 환경이 변화하면서 프론트엔드가 맡는 역할도 달라지고 있죠. 이번 글에서는 Web2와 Web3의 차이점부터, 프론트엔드의 핵심 역할과 실제 코드 예시까지 정리해보겠습니다.


1. Web2 vs Web3

Web2와 Web3는 구조적으로 유사해 보일 수 있지만, 사용하는 기술 환경이 완전히 다르기 때문에 처음에는 진입 장벽을 느끼기 쉽습니다. 이를 아래 표로 정리하면 다음과 같습니다.

📊 Web2와 Web3 비교

구분 Web2 프론트엔드 Web3 프론트엔드
백엔드 REST API 서버 스마트 컨트랙트
인증 방식 JWT, 세션 등 지갑 로그인 (Metamask 등)
데이터 저장 DB (MySQL 등) 블록체인, IPFS 등
주요 연동 API, SDK Web3 라이브러리 (ethers.js, web3.js 등)

 

위 표를 보면 Web3에서도 프론트엔드-백엔드 구조는 여전하지만, 각 구성 요소의 성격이 완전히 다름을 알 수 있습니다. 특히 다양한 블록체인 네트워크(Ethereum, Solana, Polygon 등)의 등장으로 인해 각 생태계에 맞춘 개발이 중요해졌습니다.


2. Web3 프론트엔드 구성 요소 자세히 보기

✅ 스마트 컨트랙트

Web3에서 스마트 컨트랙트는 Web2의 서버(Service layer) 역할을 합니다. 비즈니스 로직을 담고 있으며, 블록체인 위에서 실행됩니다. Solidity 언어를 사용하며, 클래스 구조와 유사합니다.

📌 간단한 스마트 컨트랙트 예시 (Solidity)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Counter {
    uint256 public count;

    function increment() public {
        count += 1;
    }

    function getCount() public view returns (uint256) {
        return count;
    }
}
  • count: 상태 변수로 저장됨 (블록체인 상 저장됨)
  • increment(): 트랜잭션을 발생시켜 값을 1 증가시킴
  • getCount(): 현재 값을 조회하는 view 함수 (가스비 X)

✅ 지갑 로그인

Web2에서는 이메일·소셜 로그인 등으로 사용자를 인증했다면, Web3에서는 지갑을 통해 서명 기반 로그인 방식이 일반적입니다.

  • 대표 지갑: Metamask, WalletConnect, Kaikas 등
  • 최근에는 Google/Apple 계정 기반으로 지갑을 만들 수 있는 소셜 로그인 지갑도 많이 생겼습니다.

✅ 블록체인

Web2에서의 DB 역할을 합니다. 스마트 컨트랙트에서 실행된 데이터는 블록체인에 영구 저장되며, 누구나 열람 가능하고 위조가 불가능합니다.


✅ Web3 라이브러리

Web2에서 REST API를 호출했다면, Web3에서는 ethers.jsweb3.js를 통해 스마트 컨트랙트와 직접 상호작용합니다.

📌 ethers.js 예시: 프론트에서 스마트 컨트랙트와 통신

import { ethers } from "ethers";

// 1. 컨트랙트 주소와 ABI
const contractAddress = "0x1234567890abcdef1234567890abcdef12345678";
const contractABI = [
  "function getCount() view returns (uint256)",
  "function increment()"
];

// 2. provider와 signer 준비 (지갑 연결 필요)
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();

// 3. 컨트랙트 인스턴스 생성
const counterContract = new ethers.Contract(contractAddress, contractABI, signer);

// 4. 함수 호출
async function getCount() {
  const count = await counterContract.getCount();
  console.log("현재 count:", count.toString());
}

async function incrementCount() {
  const tx = await counterContract.increment(); // 트랜잭션 발생
  await tx.wait(); // 블록에 기록될 때까지 대기
  console.log("카운트 증가 완료");
}

🧠 getCount()는 읽기 함수로 가스비가 들지 않지만, increment()는 쓰기 함수로 서명과 트랜잭션 처리가 필요합니다.

3. Ethereum, Solana

구분 Ethereum Solana
스마트 컨트랙트 언어 Solidity Rust (Anchor 프레임워크 사용)
주요 라이브러리 ethers.js, web3.js @solana/web3.js, @project-serum/anchor
트랜잭션 처리 느리지만 탈중앙성 강함 (PoS/PoW → 현재 PoS) 빠른 처리 속도 (PoH + PoS)
지갑 MetaMask, WalletConnect 등 EVM 기반 Phantom, Solflare 등 Solana 전용 지갑
ABI 사용 여부 필수 (프론트에서 ABI로 함수 접근) X (Anchor IDL 또는 직접 구조화 필요)
컨트랙트 주소 접근 단일 주소 사용 프로그램 ID, 계정 주소 구분 필요
읽기/쓰기 구분 명확 (view/pure vs transaction) 계정 상태 조회와 서명 구조가 복잡함

✅ 예시 비교: 이더리움 vs 솔라나에서 getCount 호출

🔹 이더리움 (ethers.js)

const contract = new ethers.Contract(contractAddress, abi, signer);
const count = await contract.getCount(); // 간단한 함수 호출

🔹 솔라나 (web3.js + Anchor IDL)

const provider = new Anchor.AnchorProvider(connection, wallet, {});
const program = new Anchor.Program(idl, programId, provider);
const account = await program.account.counter.fetch(counterAccountAddress);
console.log("현재 count:", account.count.toNumber());

📌 Solana는 ‘프로그램’과 ‘계정’을 분리하여 접근하며, 태는 ‘계정’에 저장된다.

4. Web3 프론트엔드 프로젝트 폴더 구조

처음 웹 3 프로젝트를 빌딩하다보면 프로젝트 구조를 어떻게 세워야할지 난감한 때가 오게된다. 다음과 같이 폴더를 정리하면 좀 더 쉽게 정리가 가능하다.

📦 your-dapp/
├── 📁 public/
│   └── favicon.ico
├── 📁 src/
│   ├── 📁 abi/               # 스마트 컨트랙트 ABI JSON 파일 보관
│   ├── 📁 assets/            # 이미지, 폰트 등 정적 리소스
│   ├── 📁 components/        # 공통 UI 컴포넌트 (Button, Navbar 등)
│   ├── 📁 contracts/         # 스마트 컨트랙트 주소 등 관련 정보
│   ├── 📁 hooks/             # 커스텀 훅 (useWallet, useContract 등)
│   ├── 📁 pages/             # 각 페이지 단위 컴포넌트
│   ├── 📁 utils/             # 블록체인 유틸 함수 (주소 포맷팅, 트랜잭션 처리 등)
│   ├── 📁 contexts/          # 상태 공유용 Context (예: WalletContext)
│   ├── 📁 config/            # 네트워크 설정, 환경변수 관리
│   ├── 📁 styles/            # 전역 스타일, Tailwind, SCSS 등
│   ├── App.jsx              # 앱 라우팅 설정
│   ├── index.jsx            # 진입점 (ReactDOM 렌더링)
├── .env                     # 환경 변수 (RPC URL, Contract 주소 등)
├── package.json
└── README.md

🔍 각 폴더 설명

폴더/파일 설명
abi/ 컴파일된 스마트 컨트랙트 ABI를 JSON으로 저장해서 불러오기 용이
contracts/ 배포된 컨트랙트 주소, 네트워크 ID 등 정리
hooks/ useWallet(), useContract(), useBalance() 등의 커스텀 훅으로 Web3 로직 모듈화
contexts/ 지갑 상태, 로그인 여부, 현재 네트워크 등을 Context API로 전역 공유
utils/ 트랜잭션 헬퍼 함수, 토큰 포맷팅 등 유틸 함수
config/ 지원 네트워크, RPC URL, 체인 ID 등 환경설정 모음
.env 프로젝트 환경에 따라 컨트랙트 주소, RPC URL을 바꿀 수 있도록 외부에서 주입

다음에는 프론트엔드와 지갑 및 컨트랙트 연결 구조를 집중적으로 알아보겠습니다.

반응형

+ Recent posts