[React Native] 스팀커넥트(Steemconnect) 로그인 만들기
이번에는 리액트네이티브로 스팀커넥트 로그인을 구현합니다. 저는 스팀커넥트를 선호하는 편은 아닙니다. 하지만 편리함 때문에 사용하고 있습니다.
"[React Native] 인스타그램 UI 만들기" 시리즈를 이어서 로그인까지 구현해보도록 하겠습니다. 이전에 작성했던 개발환경을 그대로 사용합니다.
로그인 화면 만들기
./Components/LoginScreen.js
파일 생성합니다. LoginScreen 컴포넌트에는 로그인 버튼을 보여줄 것입니다.
import React, { Component } from 'react';
import { StyleSheet, View, Modal } from 'react-native';
import { Icon, Container, Button, Text } from 'native-base';
export default class LoginScreen extends Component {
static navigationOptions = {
title: 'Login',
}
render() {
return (
<Container style={styles.container}>
<View style={{justifyContent:'center',alignItems: 'center'}}>
<Button iconLeft primary>
<Icon name="login" type="AntDesign" />
<Text>Steemconnect Login</Text>
</Button>
</View>
</Container>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
그다음 ./App.js
파일을 수정합니다.
import MainScreen from './Components/MainScreen';
import LoginScreen from './Components/LoginScreen';
const AppStackNavigator = createStackNavigator({
Login: { screen: LoginScreen },
Main: { screen: MainScreen }
});
// ... 일부 코드 생략 ...
LoginScreen 컴포넌트를 import 합니다. 그리고
AppStackNavigator
에 LoginScreen을 등록합니다. 로그인 체크 여부를 해서 LoginScreen 와 MainScreen를 분기해야 하지만, 나중에 고민해봅시다.
여기까지 작업하고 화면을 확인해봅니다. 심플하게 로그인 버튼 하나만 덩그러니 있습니다.
Steemconnect 모달창 만들기
그다음에는 스팀커넥트 로그인 화면을 보여줄 모달창을 구현합니다. 스팀커넥트를 구현하기 위해서 steemconnect
라이브러리를 설치합니다.
Steemconnect 라이브러리 설치
$ yarn add steemconnect
steemconnect
라이브러리 사용 방법은 공식 문서를 참고하세요.
./config.js
파일을 생성합니다. config.js
에는 스팀커넥트를 생성하는데 필요한 설정값을 입력합니다.
module.exports = {
SC2_APP_ID=스팀커넥트 앱ID
SC2_CALLBACK_URL=스팀커넥트 콜백URL
}
그다음 ./steemConnect.js
파일을 생성합니다. steemConnect.js
파일에서는 steemconnect
객체를 초기화하여 생성합니다.
import sc2 from 'steemconnect';
import Config from './config';
const api = sc2.Initialize({
app: Config.SC2_APP_ID,
callbackURL: Config.SC2_CALLBACK_URL,
scope: ['vote','comment','delete_comment','comment_options','custom_json','claim_reward_balance','offline']
});
export default api;
scope
에는 사용가능한 모든 권한을 다 입력해보았습니다. 사실 offline 권한을 얻기 위해서는response_type=code
옵션을 사용해야합니다. 하지만 별도의 요청 서버가 필요하기 때문에 이번에는 사용하지 않았습니다.
./Components/SteemConnectModal.js
파일을 생성합니다.
import React, { Component } from 'react';
import { WebView } from 'react-native';
import { Icon, Container, Button, Header, Right } from 'native-base';
import steemConnect from '../steemConnect';
class SteemConnectModal extends Component {
// webview 상태 변경 체크
_onNavigationStateChange = (event) => {
if (event.url.indexOf('?access_token') > -1) {
this.webview.stopLoading();
try {
const tokens = {};
// 콜백 URL에서 accessToken 정보 추출하기
let params = event.url.split('?')[1];
params = params.split('&');
params.forEach(e => {
const [key, val] = e.split('=');
tokens[key] = val;
});
// console.log('tokens:', tokens);
// 스팀커넥트 인증 성공
this.props.onSteemconnectSuccess(tokens);
} catch (error) {
console.log(error);
}
}
};
render() {
// 로그인 URL 생성
const link = steemConnect.getLoginURL();
// console.log(link);
return (
<Container style={{ flex: 1 }}>
<Header>
<Right>
<Button icon transparent
onPress={() => {
this.props.handleOnModalClose()
}}><Icon name='ios-close'/></Button>
</Right>
</Header>
<WebView
source={{ uri: link }}
onNavigationStateChange={this._onNavigationStateChange}
ref={ref => { this.webview = ref }}
/>
</Container>
);
}
}
export default SteemConnectModal;
_onNavigationStateChange()
함수는 WebView 컴포넌트의 URL 변화를 감지합니다. 그래서 스팀커넥트 콜백URL로 넘어온 토큰값을 가져옵니다.steemConnect.getLoginURL()
은 스팀로그인 URL을 생성합니다.https://steemconnect.com/oauth2/authorize?client_id=앱ID&redirect_uri=콜백URL&scope=vote,comment,delete_comment,comment_options,custom_json,claim_reward_balance,offline
형태의 URL이 생성됩니다.
./Components/LoginScreen.js
파일을 수정합니다. 이제 로그인 버튼을 누르면 SteemConnectModal 컴포넌트가 나타나게 합니다.
import SteemConnectModal from './SteemConnectModal';
import steemConnect from '../steemConnect';
export default class LoginScreen extends Component {
static navigationOptions = {
title: 'Login',
}
constructor(props) {
super(props);
this.state = {
modalVisible: false,
username: null,
}
}
// 모달창 닫기
_handleOnModalClose = () => {
this.setState({ modalVisible: false });
}
// 스팀커넥트 성공
_onSteemconnectSuccess = (tokens) => {
this.setState({ modalVisible: false });
// console.log(tokens);
// AccessToken 셋팅
steemConnect.setAccessToken(tokens.access_token);
// 계정 정보 조회
steemConnect.me().then(({ account }) => {
const { profile } = JSON.parse(account.json_metadata);
console.log('profile', profile);
this.setState({ username: profile.name });
});
}
render() {
const { username } = this.state;
return (
<Container style={styles.container}>
<View style={{justifyContent:'center',alignItems: 'center'}}>
{
username ? <Text>{ username }님 환영합니다.</Text> :
<Button
onPress={() => {
this.setState({ modalVisible: true })
}}
iconLeft primary>
<Icon name="login" type="AntDesign" />
<Text>Steemconnect Login</Text>
</Button>
}
</View>
{/** 스팀커넥트 모달창 **/}
<Modal
animationType="slide"
transparent={false}
visible={this.state.modalVisible}>
<SteemConnectModal
handleOnModalClose={this._handleOnModalClose}
onSteemconnectSuccess={this._onSteemconnectSuccess}
/>
</Modal>
</Container>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
// ... 일부 코드 생략 ...
- 로그인이 되기전에는 로그인 버튼이 보이고, 로그인이 되고 나면 사용자 이름이 보입니다.
_handleOnModalClose()
함수의 기능은 모달창을 닫습니다._onSteemconnectSuccess()
함수는 스팀커넥트 로그인이 성공하면,access_token
을 전달받습니다. 그리고 로그인한 사용자 이름을 출력합니다.
아래는 완성된 앱 화면입니다.
구현된 소스는 깃허브 에 업로드 되어있습니다.
여기까지 읽어주셔서 감사합니다.
짱짱맨 호출에 응답하여 보팅하였습니다.
일부코드 생략이라니 아쉽습니다만 ㅎㅎㅎ 그래도 이번에도 멋집니다. ㅋㅋㅋ
코드가 너무 길어서 일부 생략하였네요. 깃허브에 가면 모든 코드 내용을 다 볼수 있습니다.
아.예. 알겠습니다. 그냥 자른 게 아니고, 미리 쓴 걸 생략하신듯한 느낌도 드는군요. ㅎㅎㅎ 깃허브 들러보겠습니다.
로그인 기능까지 있으니 정말 완전한 앱같네요.
스팀커넥트는 어떤 부분이 마음에 안드시나요?
저도 사실 잘 이해가 안되는게 busy등 이미 권한을 위임하고도 매번 active키 입력을 요구하는데 뭔가 잘못된 사용방식 때문인건지 아니면 스팀커넥트자체가 그런것인지 모르겠더군요. steemauto의 경우 한번만 위임하고 다음에는 포스팅키 로그인으로 되는데 뭔가 잘못된 사용같아요.
로그인시에 매번 active키를 요구하는게 불안합니다. 이미 해당앱에 포스팅 권한을 위임한 경우에는 다른 방식을 사용해도 될텐데요. 그리고 포스팅 권한을 위임한 앱이 벌써 10개가 넘어가네요. 스팀커넥트를 사용한 앱의 키는 스팀커넥트 서버에서 관리되고 있는 것으로 알고 있는데, 이게 과연 안전한건지 의문도 듭니다.
제가 이해하는 바로는 키 자체가 저장되지는 않습니다. 심지어 포스팅 권한 부여한것도 권한을 부여한 것이지 포스팅 private key가 스팀커넥트에 저장되는 것은 아닙니다. 물론 이 과정은 믿어야하는 것이고 일단 오픈소스이고 서버에는 다른버전을 올려놓지 않았다면ㅎㅎ 믿어줘야겠죠ㅎㅎ
하지만 계속 액티브키로 로그인을 하게 만드는 방식은 참으로 문제입니다.
곰돌이가 @anpigon님의 소중한 댓글에 $0.018을 보팅해서 $0.005을 살려드리고 가요. 곰돌이가 지금까지 총 2827번 $34.573을 보팅해서 $35.131을 구했습니다. @gomdory 곰도뤼~
busy는 포스팅 외에 액티브 키를 써야만 하는 기능들을 몇개 가지고 있지 않나요? 파워업이라던가 스팀 전송이라던가?
scope에서 voting, comment만 사용한다면, 포스팅 키로 토큰 만들 수 있게 하면 괜찮을 거같네요. 지금은 voting, comment만 사용한다고 해도 액티브키를 요청하니깐요.
음.. 믿고 사용하는거 같습니다.
맞습니다. 불안감이 조금은 있지만 그냥 믿고 사용하는 겁니다.
안전하게 사용 하려면 스팀커넥트에 액티비키를 입력하지 않고 포스팅키로 직접 로그인 하는 것이 좋겠지요.
@anpigon님도 태그합니다.
저도 처음엔 powerup, transfer등을 쉽게해주려고(아무래도 busy는 좀더 사용자 친화적으로 나온거니까요) 그런줄 알았어요. 하지만 해보시면 아시겠지만 다시 active key를 요구합니다.
제 추측은 이전 스팀커넥트 버전에서는 한번 인증하면 해당 암호를 안 물었던 것이 아닐까요? 그러다 바뀌었는데 여전히 이전 방식대로 쓰고 있는 것 같기도 하고. 아무튼 스팀오토를 봤을때도 그렇고 권한부여라는 컨셉상으로도 한번 부여했으면 posting key로 할 수 있는 것은 다 되야하므로 불필요한 (잘못된) 인증방식인 것 같아요. 어제 깃허브에 질문해놓았으니 답이 오면 관련 포스팅을 하려고요. 감사합니다.
그리고 어제 실제 스팀오토 로그인되는 과정을 보고 포스팅키로만 로그인을 하니 이미 권한이 부여됐음에도 보팅, 포스팅을 못하더군요. 제 생각엔 api를 잘못사용해서 그런것 같아요. 어제 처음써본 steempeak도 마찬가지더군요.
제가 이해를 잘못하고 있는 것일 수도 있겠네요. 하지만 스팀커넷트 서버 API 오픈소스를 보면, 포스팅 키를
process.env.BROADCASTER_POSTING_WIF
에서 가져와서broadcast.send
하고 있습니다.https://github.com/steemscript/steemconnect-api/blob/master/routes/api.js#L126
제가 염려하는 부분은 우리가 권한을 위임한 앱의 포스팅키 유출입니다. 하지만 서버에 안전하게 잘 보관되고 있을 것이라고 믿고 있습니다. dclick, busy 등 대부분의 dapp이 스팀커넥트 로그인 방식만 지원해서, 어쩔수 없이 사용하고 있습니다. steem에도 metamask나 scatter와 비슷한 크롬 확장 익스텐션이 나왔으면 좋겠습니다.
아 제 이전글에 대한 답글이군요. 서로 미스커뮤니케이션이 있었던 것 같습니다. 저는 사용자의 키를 저장한다고 말씀하신 줄 알았는데 "스팀커넥트를 사용한 앱의 키"라고 앱의 키를 말씀하신거군요ㅎㅎ 어쩐지 이상하다 했습니다. 제가 제대로 안읽었네요 죄송합니다ㅠㅠ
네 말씀하신대로 키는 서버에 저장을 하든 env에 실행할때 세팅을 하든지 할테니 털리면 위험하죠ㅠㅠ 어쩔수 없는 것 같습니다. 말씀하신대로 metamask같은게 나와야할텐데 만들고는 있긴하던데 어찌되가는지는 모르겠네요. 감사합니다.
답변 감사합니다. 암호화폐 개인키는 로그인 비밀번호인 동시에 화폐를 사용할 수 있는 키다 보니 민감합니다. 이더리움은 메타마스크가 개인키를 보호해주고, 이오스는 스캐터가 보호해주는데, 스팀은 스팀커넥트가 보호해준다? ㅎㅎ
그리고 스텔라X는 소스를 분석해보니, 사용자 지갑 개인키를 서버에서 발급하는 키로 암호화해서 브라우저 로컬스토리지에 저장하고 있더군요. 암호화키는 개인마다 발급되는듯 합니다. 오픈소스가 아니다보니 정확한 분석은 아닐수 있습니다.
곰돌이가 @anpigon님의 소중한 댓글에 $0.017을 보팅해서 $0.006을 살려드리고 가요. 곰돌이가 지금까지 총 2850번 $34.882을 보팅해서 $35.351을 구했습니다. @gomdory 곰도뤼~
멋져요! 최고최고!
궁금한게 있는데요.
config의 경우는 따로 라이브러리 안쓰고
javascript 파일에 값 넣고 클래스로 만들어 놓고
import해서 사용하신거죠?
네 맞습니다.
config.js
는 그냥 import 해서 사용했습니다. 사실은 react-native-config 라이브러리를 사용하고 싶었는데, expo 에서는 env 파일을 못읽어 오더군요. 리액트 네이티브의 경우에는 env를 사용해도 보안상 이점이 없어서, 그냥 config.js를 사용했습니다. ㅋㅋHi @anpigon!
Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your UA account score is currently 2.881 which ranks you at #11822 across all Steem accounts.
Your rank has improved 5 places in the last three days (old rank 11827).
In our last Algorithmic Curation Round, consisting of 191 contributions, your post is ranked at #112.
Evaluation of your UA score:
Feel free to join our @steem-ua Discord server
Hey man! Thanks a lot, you made me acomplish this task. Really really nice explanation ( I had to translate it in english but was so worth! )
Thanks a lot! <3
Thanks. Thanks. And My english skill is not good. sorry.