티스토리 뷰
🌐 이전 게시글
🛒 로그인 절차
- 클라이언트 => 서버 websocket 연결 요청
- websocket 이 연결되면 websocket을 통해 서버 => 클라이언트 세션 아이디 전달
- 세션 아이디와 함께 클라이언트 => 서버 로그인 QR 코드 요청
- 서버 => 클라이언트 세션 아이디와 매칭되는 로그인 요청을 생성하여 저장한 뒤, QR 코드를 생성하여 전달
- 사용자의 Polygon ID 앱 => 클라이언트 로그인 QR 코드 스캔
- Polygon ID 앱에서 profile 선택, 연결
- Polygon ID 앱 => 서버 로그인 토큰과 함께 콜백 요청
- 콜백 요청을 받으면 서버 => 클라이언트 websocket을 통해 이벤트(진행 중) 전달
- 서버에서 토큰 검증
- 토큰 검증이 완료되면 서버 => 클라이언트 websocket을 통해 이벤트(완료) 전달
- 서버 => Polygon ID 앱 인증 성공 응답 전달
- 클라이언트 => 서버 websocket 연결 종료
🏃♂️ 코드 실행 방법
저장소 클론
$git clone https://github.com/piatoss3612/polygon-id-template.git
ngrok 실행
$ ngrok http 8080
콜백 URL이 HTTPS 연결이 필요하므로 ngrok을 통해 로컬호스트의 8080 포트를 포워딩해 줍니다.
.env 파일 설정
server
# Update HOSTED_SERVER_URL with the ngrok url returned when you run `ngrok http 8080`
HOSTED_SERVER_URL=<ngrok을 통해 로컬호스트 포트로 포워딩하는 HTTPS URL>
# Update RPC_URL_MUMBAI with your Polygon Mumbai API Key: https://alchemy.com/?r=zU2MTQwNTU5Mzc2M
RPC_URL_MUMBAI="https://polygon-mumbai.g.alchemy.com/v2/your-api-key"
FRONTEND_URL="http://localhost:3000"
VERIFIER_DID="did:polygonid:polygon:mumbai:2qDyy1kEo2AYcP3RT4XGea7BtxsY285szg6yP9SPrs"
'HOSTED_SERVER_URL'과 'RPC_URL_MUMBAI'의 값을 설정해 줍니다. RPC URL은 alchemy가 아닌 다른 프로바이더의 URL을 사용해도 좋지만, Polygon Mumbai 네트워크와 연결된 것을 사용해야 합니다.
frontend
# Update with the ngrok url returned when you ran `ngrok http 8080` on the server folder
REACT_APP_VERIFICATION_SERVER_PUBLIC_URL=<ngrok을 통해 로컬호스트 포트로 포워딩하는 HTTPS URL>"
REACT_APP_VERIFICATION_SERVER_LOCAL_HOST_URL="http://localhost:8080"
server와 마찬가지로 ngrok URL을 설정해 줍니다.
서버 실행
$ cd server
$ go run .
새로운 터미널 창을 열고 실행합니다.
클라이언트 실행
$ cd frontend
$ npm i --force # --force는 오류가 날 경우 강제로 설치
$ npm start
마찬가지로 새로운 터미널 창을 열고 실행합니다.
🎓 코드 살펴보기
로그인 핸들러
func GetLoginRequest(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
sessionId := r.URL.Query().Get("sessionId")
_, err := uuid.Parse(sessionId)
if err != nil {
http.Error(w, "invalid session id", http.StatusBadRequest)
return
}
hub.send <- Message{
Type: EventMessage,
ID: ID(sessionId),
Event: Event{
Fn: "getLoginQr",
Status: InProgress,
Data: sessionId,
},
}
callback := fmt.Sprintf("%s/api/login-callback?sessionId=%s",
os.Getenv("HOSTED_SERVER_URL"),
sessionId,
)
sender := "did:polygonid:polygon:mumbai:2qDyy1kEo2AYcP3RT4XGea7BtxsY285szg6yP9SPrs"
var request protocol.AuthorizationRequestMessage = auth.CreateAuthorizationRequestWithMessage(
"Login to Polygon",
"Your Polygon ID",
sender,
callback,
)
request.ID = sessionId
request.ThreadID = sessionId
requestMap[sessionId] = request
hub.send <- Message{
Type: EventMessage,
ID: ID(sessionId),
Event: Event{
Fn: "getLoginQr",
Status: Done,
Data: request,
},
}
msgBytes, _ := json.Marshal(request)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(msgBytes)
}
1. 세션 id 확인
sessionId := r.URL.Query().Get("sessionId")
_, err := uuid.Parse(sessionId)
if err != nil {
http.Error(w, "invalid session id", http.StatusBadRequest)
return
}
URL 쿼리 파라미터로부터 세션 id를 가져와 유효한 id인지 검사합니다.
2. 로그인 요청 생성
callback := fmt.Sprintf("%s/api/login-callback?sessionId=%s",
os.Getenv("HOSTED_SERVER_URL"),
sessionId,
)
sender := "did:polygonid:polygon:mumbai:2qDyy1kEo2AYcP3RT4XGea7BtxsY285szg6yP9SPrs"
var request protocol.AuthorizationRequestMessage = auth.CreateAuthorizationRequestWithMessage(
"Login to Polygon",
"Your Polygon ID",
sender,
callback,
)
request.ID = sessionId
request.ThreadID = sessionId
requestMap[sessionId] = request
콜백 URL, 검증자의 DID, 검증 이유 그리고 메시지를 지정하여 새로운 로그인 요청을 생성합니다.
3. 클라이언트에게 응답
msgBytes, _ := json.Marshal(request)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(msgBytes)
생성한 요청을 JSON 형식으로 인코딩하여 응답 바디에 담아 클라이언트에게 전송합니다.
로그인 핸들러
func LoginCallback(w http.ResponseWriter, r *http.Request) {
sessionId := r.URL.Query().Get("sessionId")
_, err := uuid.Parse(sessionId)
if err != nil {
http.Error(w, "invalid session id", http.StatusBadRequest)
return
}
tokenBytes, _ := io.ReadAll(r.Body)
defer r.Body.Close()
authRequest, ok := requestMap[sessionId]
if !ok {
w.WriteHeader(http.StatusBadRequest)
return
}
hub.send <- Message{
Type: EventMessage,
ID: ID(sessionId),
Event: Event{
Fn: "handleLogin",
Status: InProgress,
Data: authRequest,
},
}
ipfsURL := "https://ipfs.io"
contractAddress := "0x134B1BE34911E39A8397ec6289782989729807a4"
resolverPrefix := "polygon:mumbai"
keyDIR := "./keys"
var verificationKeyLoader = &loaders.FSKeyLoader{
Dir: keyDIR,
}
resolver := state.ETHResolver{
RPCUrl: os.Getenv("RPC_URL_MUMBAI"),
ContractAddress: common.HexToAddress(contractAddress),
}
resolvers := map[string]pubsignals.StateResolver{
resolverPrefix: &resolver,
}
verifier, err := auth.NewVerifier(
verificationKeyLoader,
resolvers,
auth.WithIPFSGateway(ipfsURL),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
authResponse, err := verifier.FullVerify(
r.Context(),
string(tokenBytes),
authRequest.(protocol.AuthorizationRequestMessage),
pubsignals.WithAcceptedStateTransitionDelay(time.Minute*5),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
hub.send <- Message{
Type: EventMessage,
ID: ID(sessionId),
Event: Event{
Fn: "handleLogin",
Status: Done,
Data: authResponse,
},
}
userID := authResponse.From
messageBytes := []byte("User with ID " + userID + " Successfully authenticated")
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(messageBytes)
}
1. 세션 id 확인
sessionId := r.URL.Query().Get("sessionId")
_, err := uuid.Parse(sessionId)
if err != nil {
http.Error(w, "invalid session id", http.StatusBadRequest)
return
}
URL 쿼리 파라미터로부터 세션 id를 가져와 유효한 id인지 검사합니다.
2. 토큰 및 로그인 요청 확인
tokenBytes, _ := io.ReadAll(r.Body)
defer r.Body.Close()
authRequest, ok := requestMap[sessionId]
if !ok {
w.WriteHeader(http.StatusBadRequest)
return
}
콜백 요청은 Polygon ID 앱으로부터 들어오는 요청으로, 로그인을 요청한 사용자의 유효성을 증명하기 위한 토큰이 요청 바디에 담겨져 있습니다. 토큰을 읽어들이고 나면 로그인 요청이 서버에 저장되어 있는지 확인합니다. 만약 로그인 요청이 존재하지 않은 상태에서 콜백 요청이 들어왔다면 이는 유효하지 않은 요청입니다.
3. 이벤트 전달 (진행 중)
hub.send <- Message{
Type: EventMessage,
ID: ID(sessionId),
Event: Event{
Fn: "handleLogin",
Status: InProgress,
Data: authRequest,
},
}
4. 검증자 생성
ipfsURL := "https://ipfs.io"
contractAddress := "0x134B1BE34911E39A8397ec6289782989729807a4"
resolverPrefix := "polygon:mumbai"
keyDIR := "./keys"
var verificationKeyLoader = &loaders.FSKeyLoader{
Dir: keyDIR,
}
resolver := state.ETHResolver{
RPCUrl: os.Getenv("RPC_URL_MUMBAI"),
ContractAddress: common.HexToAddress(contractAddress),
}
resolvers := map[string]pubsignals.StateResolver{
resolverPrefix: &resolver,
}
verifier, err := auth.NewVerifier(
verificationKeyLoader,
resolvers,
auth.WithIPFSGateway(ipfsURL),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
5. 검증
authResponse, err := verifier.FullVerify(
r.Context(),
string(tokenBytes),
authRequest.(protocol.AuthorizationRequestMessage),
pubsignals.WithAcceptedStateTransitionDelay(time.Minute*5),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
6. 이벤트 전달 (완료)
hub.send <- Message{
Type: EventMessage,
ID: ID(sessionId),
Event: Event{
Fn: "handleLogin",
Status: Done,
Data: authResponse,
},
}
7. 검증 성공 응답
userID := authResponse.From
messageBytes := []byte("User with ID " + userID + " Successfully authenticated")
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(messageBytes)
이 응답은 클라이언트로 전달되지 않고 사용자의 Polygon ID 앱으로 전달됩니다.
📖 참고 자료
'Go > 코딩 하기' 카테고리의 다른 글
[Go] Polygon ID와 Websocket을 사용한 신원 인증 - 4. 신원 인증 (0) | 2023.12.21 |
---|---|
[Go] Polygon ID와 Websocket을 사용한 신원 인증 - 2. Websocket (1) | 2023.12.21 |
[Go] Polygon ID와 Websocket을 사용한 신원 인증 - 1. Polygon ID (0) | 2023.12.20 |
[Go] 우아하게 종료하기 (Graceful shutdown) (0) | 2023.10.24 |
[Go] 아파치 카프카를 사용한 간단한 이벤트 기반 서비스 구현 (0) | 2023.10.15 |